Merge pull request #81 from saifullah-N/v1.2.1-without-em60

V1.2.1 without em60
This commit is contained in:
Sathish Kumar
2023-02-06 13:34:14 +08:00
committed by GitHub
122 changed files with 30151 additions and 5911 deletions

7
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/blob/v0.241.1/containers/debian/.devcontainer/base.Dockerfile
# [Choice] Debian version (use bullseye on local arm64/Apple Silicon): bullseye, buster
ARG VARIANT=bullseye
FROM mcr.microsoft.com/vscode/devcontainers/base:${VARIANT}
COPY ./install_tools.sh /tmp/install_tools.sh
RUN /tmp/install_tools.sh

View File

@@ -0,0 +1,36 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/blob/v0.241.1/containers/debian
{
"name": "Debian",
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick an Debian version: bullseye, buster
// Use bullseye on local arm64/Apple Silicon.
"args": {
"VARIANT": "bullseye"
}
},
"runArgs": [
// The primary reason this is here is to enable loopback devices,
// which are used for mounting disk images during builds, etc.
"--privileged"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
//
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
"features": {
"sshd": "latest"
},
"extensions": [
"ms-azuretools.vscode-docker",
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"ms-vscode.makefile-tools",
"ms-python.python",
"svelte.svelte-vscode",
"redhat.vscode-yaml",
"ryu1kn.partial-diff"
]
}

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env -S bash -e
APT_PACKAGES=(
"build-essential"
"git"
"wget"
"binfmt-support"
"qemu"
"gcc-9"
"parted"
"udev"
"zerofree"
"gcc-avr"
"avr-libc"
"avrdude"
"python3"
"python3-pip"
"python3-tornado"
"inetutils-ping"
"curl"
"unzip"
"python3-setuptools"
"gcc-arm-linux-gnueabihf"
"bc"
"vim"
"locate"
"sudo"
"sshpass"
)
apt-get update
apt-get upgrade -y
apt-get install -y "${APT_PACKAGES[@]}"
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 9
/usr/bin/python3 -m pip install -U yapf
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt-get install -y nodejs
mkdir -p /root/.ssh
cat > /root/.ssh/config <<- END
Host onefinity
User bbmc
END

31
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,31 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/node_modules": true,
"**/build": true,
"**/dist": true,
"**/rpi-share": true,
"**/rpi-root": true
},
"search.exclude": {
"**/bower_components": true,
"**/*.code-search": true,
"**/node_modules": true,
"**/build": true,
"**/dist": true,
"**/rpi-share": true,
"**/rpi-root": true
},
"explorer.excludeGitIgnore": true,
"git.ignoredRepositories": [
"rpi-share/camotics",
"rpi-share/cbang"
],
"git.autofetch": false,
"git.confirmSync": false
}

View File

@@ -22,13 +22,8 @@ RSYNC_OPTS := $(RSYNC_EXCLUDE) -rv --no-g --delete --force
VERSION := $(shell sed -n 's/^.*"version": "\([^"]*\)",.*$$/\1/p' package.json) VERSION := $(shell sed -n 's/^.*"version": "\([^"]*\)",.*$$/\1/p' package.json)
PKG_NAME := bbctrl-$(VERSION) PKG_NAME := bbctrl-$(VERSION)
PUB_PATH := root@buildbotics.com:/var/www/buildbotics.com/bbctrl
BETA_VERSION := $(VERSION)-rc$(shell ./scripts/next-rc)
BETA_PKG_NAME := bbctrl-$(BETA_VERSION)
SUBPROJECTS := avr boot pwr jig SUBPROJECTS := avr boot pwr jig
WATCH := src/pug src/pug/templates src/stylus src/js src/resources Makefile
WATCH += src/static
ifndef HOST ifndef HOST
HOST=onefinity HOST=onefinity
@@ -38,16 +33,12 @@ ifndef PASSWORD
PASSWORD=onefinity PASSWORD=onefinity
endif endif
all: $(HTML) $(RESOURCES) all: $(HTML) $(RESOURCES)
@for SUB in $(SUBPROJECTS); do $(MAKE) -C src/$$SUB; done @for SUB in $(SUBPROJECTS); do $(MAKE) -C src/$$SUB; done
pkg: all $(AVR_FIRMWARE) bbserial pkg: all $(AVR_FIRMWARE) bbserial
./setup.py sdist ./setup.py sdist
beta-pkg: pkg
cp dist/$(PKG_NAME).tar.bz2 dist/$(BETA_PKG_NAME).tar.bz2
bbserial: bbserial:
$(MAKE) -C src/bbserial $(MAKE) -C src/bbserial
@@ -58,12 +49,6 @@ $(GPLAN_TARGET): $(GPLAN_MOD)
$(GPLAN_MOD): $(GPLAN_IMG) $(GPLAN_MOD): $(GPLAN_IMG)
./scripts/gplan-init-build.sh ./scripts/gplan-init-build.sh
git -C rpi-share/cbang fetch
git -C rpi-share/cbang reset --hard FETCH_HEAD
git -C rpi-share/cbang checkout 18f1e963107ef26abe750c023355a5c40dd07853
git -C rpi-share/camotics fetch
git -C rpi-share/camotics reset --hard FETCH_HEAD
git -C rpi-share/camotics checkout ec876c80d20fc19837133087cef0c447df5a939d
cp ./scripts/gplan-build.sh rpi-share/ cp ./scripts/gplan-build.sh rpi-share/
chmod +x rpi-share/gplan-build.sh chmod +x rpi-share/gplan-build.sh
sudo ./scripts/rpi-chroot.sh $(GPLAN_IMG) /mnt/host/gplan-build.sh sudo ./scripts/rpi-chroot.sh $(GPLAN_IMG) /mnt/host/gplan-build.sh
@@ -75,15 +60,6 @@ $(GPLAN_IMG):
$(AVR_FIRMWARE): $(AVR_FIRMWARE):
$(MAKE) -C src/avr $(MAKE) -C src/avr
publish: pkg
echo -n $(VERSION) > dist/latest.txt
rsync $(RSYNC_OPTS) dist/$(PKG_NAME).tar.bz2 dist/latest.txt $(PUB_PATH)/
publish-beta: beta-pkg
echo -n $(BETA_VERSION) > dist/latest-beta.txt
rsync $(RSYNC_OPTS) dist/$(BETA_PKG_NAME).tar.bz2 dist/latest-beta.txt \
$(PUB_PATH)/
update: pkg update: pkg
http_proxy= curl -i -X PUT -H "Content-Type: multipart/form-data" \ http_proxy= curl -i -X PUT -H "Content-Type: multipart/form-data" \
-F "firmware=@dist/$(PKG_NAME).tar.bz2" -F "password=$(PASSWORD)" \ -F "firmware=@dist/$(PKG_NAME).tar.bz2" -F "password=$(PASSWORD)" \
@@ -100,6 +76,9 @@ node_modules: package.json
$(TARGET_DIR)/%: src/resources/% $(TARGET_DIR)/%: src/resources/%
install -D $< $@ install -D $< $@
src/svelte-components/dist/%:
cd src/svelte-components && rm -rf dist && npm run build
$(TARGET_DIR)/index.html: build/templates.pug $(TARGET_DIR)/index.html: build/templates.pug
$(TARGET_DIR)/index.html: $(wildcard src/static/js/*) $(TARGET_DIR)/index.html: $(wildcard src/static/js/*)
$(TARGET_DIR)/index.html: $(wildcard src/static/css/*) $(TARGET_DIR)/index.html: $(wildcard src/static/css/*)
@@ -108,39 +87,20 @@ $(TARGET_DIR)/index.html: $(wildcard src/js/*)
$(TARGET_DIR)/index.html: $(wildcard src/stylus/*) $(TARGET_DIR)/index.html: $(wildcard src/stylus/*)
$(TARGET_DIR)/index.html: src/resources/config-template.json $(TARGET_DIR)/index.html: src/resources/config-template.json
$(TARGET_DIR)/index.html: $(wildcard src/resources/onefinity*defaults.json) $(TARGET_DIR)/index.html: $(wildcard src/resources/onefinity*defaults.json)
$(TARGET_DIR)/index.html: $(wildcard src/svelte-components/dist/*)
$(TARGET_DIR)/%.html: src/pug/%.pug node_modules FORCE:
@mkdir -p $(shell dirname $@)
$(TARGET_DIR)/%.html: src/pug/%.pug node_modules FORCE
cd src/svelte-components && rm -rf dist && npm run build
@mkdir -p $(TARGET_DIR)/svelte-components
cp src/svelte-components/dist/* $(TARGET_DIR)/svelte-components/
@mkdir -p $(TARGET_DIR)
$(PUG) -O pug-opts.js -P $< -o $(TARGET_DIR) || (rm -f $@; exit 1) $(PUG) -O pug-opts.js -P $< -o $(TARGET_DIR) || (rm -f $@; exit 1)
pylint: clean:
pylint3 -E $(shell find src/py -name \*.py | grep -v flycheck_) rm -rf rpi-share
git clean -fxd
jshint:
./node_modules/jshint/bin/jshint --config jshint.json src/js/*.js
lint: pylint jshint
watch:
@clear
$(MAKE)
@while sleep 1; do \
inotifywait -qr -e modify -e create -e delete \
--exclude .*~ --exclude \#.* $(WATCH); \
clear; \
$(MAKE); \
done
tidy:
rm -f $(shell find "$(DIR)" -name \*~)
clean: tidy
rm -rf build html dist
@for SUB in $(SUBPROJECTS); do \
$(MAKE) -C src/$$SUB clean; \
done
dist-clean: clean
rm -rf node_modules
.PHONY: all install clean tidy pkg gplan lint pylint jshint bbserial .PHONY: all install clean tidy pkg gplan lint pylint jshint bbserial

View File

@@ -1,16 +1,28 @@
{ {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.9", "version": "1.2.1",
"homepage": "https://onefinitycnc.com/", "homepage": "https://onefinitycnc.com/",
"repository": "https://github.com/OneFinityCNC/onefinity", "repository": "https://github.com/OneFinityCNC/onefinity",
"license": "GPL-3.0+", "license": "GPL-3.0+",
"dependencies": { "scripts": {
"browserify": "", "postinstall": "cd src/svelte-components && npm i"
"jshint": "", },
"jstransformer-escape-html": "", "devDependencies": {
"jstransformer-stylus": "", "@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
"browserify": "^17.0.0",
"eslint": "^8.23.0",
"eslint-config-standard-with-typescript": "^22.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.2.5",
"eslint-plugin-promise": "^6.0.1",
"glob": "^8.0.3",
"inquirer": "^8.2.4",
"jshint": "^2.13.4",
"jstransformer-escape-html": "^1.1.0",
"jstransformer-scss": "^2.0.0",
"jstransformer-stylus": "^1.5.0",
"lodash.merge": "4.6.2", "lodash.merge": "4.6.2",
"lodash.omit": "^4.5.0", "pug-cli": "^1.0.0-alpha6"
"pug-cli": ""
} }
} }

10677
pakage-lock.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ setup(
license = pkg['license'], license = pkg['license'],
url = pkg['homepage'], url = pkg['homepage'],
package_dir = {'': 'src/py'}, package_dir = {'': 'src/py'},
packages = ['bbctrl', 'inevent', 'lcd', 'camotics'], packages = ['bbctrl', 'inevent', 'lcd', 'camotics','iw_parse'],
include_package_data = True, include_package_data = True,
entry_points = { entry_points = {
'console_scripts': [ 'console_scripts': [

View File

@@ -54,7 +54,7 @@ static struct {
line_t line; line_t line;
int section; int section;
int seg; uint32_t seg;
float iD; // Initial section distance float iD; // Initial section distance
float iV; // Initial section velocity float iV; // Initial section velocity

View File

@@ -33,6 +33,7 @@
#include "config.h" #include "config.h"
#include "command.h" #include "command.h"
#include "exec.h" #include "exec.h"
#include "estop.h"
#include "util.h" #include "util.h"
#include "state.h" #include "state.h"
@@ -94,6 +95,8 @@ static void _set_speed(float speed) {
float power = _speed_to_power(speed); float power = _speed_to_power(speed);
if (estop_triggered()) power = 0;
switch (spindle.type) { switch (spindle.type) {
case SPINDLE_TYPE_DISABLED: break; case SPINDLE_TYPE_DISABLED: break;

View File

@@ -58,6 +58,7 @@ typedef enum {
SPINDLE_TYPE_SUNFAR_E300, SPINDLE_TYPE_SUNFAR_E300,
SPINDLE_TYPE_OMRON_MX2, SPINDLE_TYPE_OMRON_MX2,
SPINDLE_TYPE_V70, SPINDLE_TYPE_V70,
SPINDLE_TYPE_EM60,
} spindle_type_t; } spindle_type_t;

View File

@@ -49,6 +49,7 @@ typedef enum {
REG_FREQ_SET, REG_FREQ_SET,
REG_FREQ_SIGN_SET, REG_FREQ_SIGN_SET,
REG_FREQ_SCALED_SET,
REG_STOP_WRITE, REG_STOP_WRITE,
REG_FWD_WRITE, REG_FWD_WRITE,
@@ -179,6 +180,17 @@ const vfd_reg_t v70_regs[] PROGMEM = {
{REG_DISABLED}, {REG_DISABLED},
}; };
const vfd_reg_t em60_regs[] PROGMEM = {
{REG_MAX_FREQ_READ, 0x0007, 0}, // Read max frequency
{REG_FREQ_SCALED_SET, 0xa001, 10000}, // Set scaled frequency
{REG_FREQ_READ, 0x9000, 0}, // Read frequency
{REG_FWD_WRITE, 0xa000, 1}, // Run forward
{REG_REV_WRITE, 0xa000, 2}, // Run reverse
{REG_STOP_WRITE, 0xa000, 5}, // Stop
{REG_STATUS_READ, 0xb000, 0}, // Read status
{REG_DISABLED},
};
static vfd_reg_t regs[VFDREG]; static vfd_reg_t regs[VFDREG];
static vfd_reg_t custom_regs[VFDREG]; static vfd_reg_t custom_regs[VFDREG];
@@ -215,6 +227,12 @@ static bool _next_state() {
else vfd.state = REG_FREQ_SET; else vfd.state = REG_FREQ_SET;
break; break;
case REG_FREQ_SCALED_SET:
if (vfd.power < 0) vfd.state = REG_REV_WRITE;
else if (0 < vfd.power) vfd.state = REG_FWD_WRITE;
else vfd.state = REG_STOP_WRITE;
break;
case REG_FREQ_SIGN_SET: case REG_FREQ_SIGN_SET:
if (vfd.power < 0) vfd.state = REG_REV_WRITE; if (vfd.power < 0) vfd.state = REG_REV_WRITE;
else if (0 < vfd.power) vfd.state = REG_FWD_WRITE; else if (0 < vfd.power) vfd.state = REG_FWD_WRITE;
@@ -344,6 +362,11 @@ static bool _exec_command() {
reg.value = vfd.power * vfd.max_freq; reg.value = vfd.power * vfd.max_freq;
break; break;
case REG_FREQ_SCALED_SET:
write = true;
reg.value = fabs(vfd.power) * reg.value;
break;
case REG_CONNECT_WRITE: case REG_CONNECT_WRITE:
case REG_STOP_WRITE: case REG_STOP_WRITE:
case REG_FWD_WRITE: case REG_FWD_WRITE:
@@ -399,6 +422,7 @@ void vfd_spindle_init() {
case SPINDLE_TYPE_SUNFAR_E300: _load(sunfar_e300_regs); break; case SPINDLE_TYPE_SUNFAR_E300: _load(sunfar_e300_regs); break;
case SPINDLE_TYPE_OMRON_MX2: _load(omron_mx2_regs); break; case SPINDLE_TYPE_OMRON_MX2: _load(omron_mx2_regs); break;
case SPINDLE_TYPE_V70: _load(v70_regs); break; case SPINDLE_TYPE_V70: _load(v70_regs); break;
case SPINDLE_TYPE_EM60: _load(em60_regs); break;
default: break; default: break;
} }

View File

@@ -1,32 +1,7 @@
/******************************************************************************\ "use strict";
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'
const api = require("./api");
const utils = require("./utils");
const merge = require("lodash.merge"); const merge = require("lodash.merge");
const config_defaults = require("../resources/onefinity_defaults.json"); const config_defaults = require("../resources/onefinity_defaults.json");
@@ -38,72 +13,82 @@ const variant_defaults = {
journeyman_x50: require("../resources/onefinity_journeyman_x50_defaults.json") journeyman_x50: require("../resources/onefinity_journeyman_x50_defaults.json")
}; };
const api = require('./api'); const z_slider_defaults = {
"Z-16 Original": {
"travel-per-rev": 4,
"min-soft-limit": -133,
"max-velocity": 3,
},
"Z-20 Heavy Duty": {
"travel-per-rev": 10,
"min-soft-limit": -160,
"max-velocity": 7,
},
};
module.exports = { module.exports = {
template: '#admin-general-view-template', template: "#admin-general-view-template",
props: ['config', 'state'], props: [ "config", "state" ],
data: function() { data: function() {
return { return {
configRestored: false,
confirmReset: false, confirmReset: false,
configReset: false,
latest: '',
autoCheckUpgrade: true, autoCheckUpgrade: true,
reset_variant: '' reset_variant: "",
} z_slider: "",
}, z_slider_variant:" ",
config:""
events: { };
latest_version: function (version) {
this.latest = version
}
}, },
ready: function() { ready: function() {
this.autoCheckUpgrade = this.config.admin['auto-check-upgrade'] this.autoCheckUpgrade = this.config.admin["auto-check-upgrade"];
}, },
methods: { methods: {
backup: function() { backup: function() {
document.getElementById('download-target').src = '/api/config/download'; document.getElementById("download-target").src = "/api/config/download";
}, },
restore_config: function() { restore_config: function() {
// If we don't reset the form the browser may cache file if name is same utils.clickFileInput("restore-config");
// even if contents have changed
$('.restore-config')[0].reset();
$('.restore-config input').click();
}, },
restore: function(e) { restore: function(e) {
var files = e.target.files || e.dataTransfer.files; const files = e.target.files || e.dataTransfer.files;
if (!files.length) return; if (!files.length) {
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; return;
} }
api.put('config/save', config).done(function (data) { const fileReader = new FileReader();
this.$dispatch('update'); fileReader.onload = async ({ target }) => {
this.configRestored = true; let config;
try {
config = JSON.parse(target.result);
} catch (error) {
console.error("Invalid config file:", error);
alert("Invalid config file");
return;
}
}.bind(this)).fail(function (error) { try {
api.alert('Restore failed', error); await api.put("config/save", config);
}) this.$dispatch("update");
}.bind(this); SvelteComponents.showDialog("Message", {
title: "Success",
message: "Configuration restored"
});
this.confirmReset= false
} catch (error) {
console.error("Restore failed:", error);
alert("Restore failed");
}
};
fr.readAsText(files[0]); fileReader.readAsText(files[0]);
}, },
reset: async function () { next: async function() {
const config = merge( const config = merge(
{}, {},
config_defaults, config_defaults,
@@ -111,40 +96,61 @@ module.exports = {
); );
try { try {
await api.put('config/save', config) await api.put("config/save", config);
this.confirmReset = false; this.confirmReset = false;
this.$dispatch('update'); this.$dispatch("update");
this.configRestored = true; this.config= config
} catch (err) { this.z_slider = true;
api.alert('Restore failed'); } catch (error) {
console.error('Restore failed', err); console.error("Restore failed:", error);
alert("Restore failed");
} }
}, },
set_z_slider: async function(){
const z_variant = merge(
{},
this.config.motors[3],
z_slider_defaults[this.z_slider_variant],
);
this.config.motors[3] = z_variant;
try {
await api.put("config/save", this.config);
this.$dispatch("update");
SvelteComponents.showDialog("Message", {
title: "Success",
message: "Configuration restored",
});
this.z_slider = false;
} catch (error) {
console.error("Z slider failed:", error);
alert("failed to set Z slider ");
}
},
check: function() { check: function() {
this.$dispatch('check') this.$dispatch("check");
}, },
upgrade: function() { upgrade: function() {
this.$dispatch('upgrade') this.$dispatch("upgrade");
}, },
upload_firmware: function() { upload_firmware: function() {
// If we don't reset the form the browser may cache file if name is same utils.clickFileInput("upload-firmware");
// even if contents have changed
$('.upload-firmware')[0].reset();
$('.upload-firmware input').click();
}, },
upload: function(e) { upload: function(e) {
var files = e.target.files || e.dataTransfer.files; const files = e.target.files || e.dataTransfer.files;
if (!files.length) return; if (!files.length) {
this.$dispatch('upload', files[0]); return;
}
this.$dispatch("upload", files[0]);
}, },
change_auto_check_upgrade: function() { change_auto_check_upgrade: function() {
this.config.admin['auto-check-upgrade'] = this.autoCheckUpgrade; this.config.admin["auto-check-upgrade"] = this.autoCheckUpgrade;
this.$dispatch('config-changed'); this.$dispatch("config-changed");
}
} }
} }
};

View File

@@ -1,177 +1,14 @@
/******************************************************************************\
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 = { module.exports = {
template: '#admin-network-view-template', template: "#admin-network-view-template",
props: ['config', 'state'],
attached: function() {
data: function () { this.svelteComponent = SvelteComponents.createComponent(
return { "AdminNetworkView",
hostnameSet: false, document.getElementById("admin-network")
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
}
}, },
detached: function() {
ready: function () { this.svelteComponent.$destroy();
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))
}
}
} }
};

View File

@@ -1,104 +1,48 @@
/******************************************************************************\ "use strict";
This file is part of the Buildbotics firmware. async function callApi(method, url, data) {
try {
const headers = {};
let body = undefined;
Copyright (c) 2015 - 2018, Buildbotics LLC if (data) {
All rights reserved. if (data instanceof FormData) {
body = data;
This file ("the software") is free software: you can redistribute it } else {
and/or modify it under the terms of the GNU General Public License, headers["Content-Type"] = "application/json; charset=utf-8";
version 2 as published by the Free Software Foundation. You should body = JSON.stringify(data);
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(); const response = await fetch(`/api/${url}`, {
method,
$.ajax(config).success(function (data, status, xhr) { headers,
d.resolve(data, status, xhr); body,
cache: "no-cache",
}).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(); if (response.ok) {
return await response.json();
} }
throw new Error(await response.text());
} catch (error) {
console.debug(`API Error: ${url}: ${error}`);
throw error;
}
}
module.exports = { module.exports = {
get: function (url, config) { get: function(url) {
return api_cb('GET', url, undefined, config); return callApi("GET", url);
}, },
put: function(url, body = undefined) {
put: function(url, data, config) { return callApi("PUT", url, body);
return api_cb('PUT', url, data, config);
}, },
delete: function(url) {
post: function(url, data, config) { return callApi("DELETE", url);
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);
}
} }
};

View File

@@ -1,201 +1,195 @@
/******************************************************************************\ "use strict";
This file is part of the Buildbotics firmware. const api = require("./api");
const cookie = require("./cookie")("bbctrl-");
const Sock = require("./sock");
const semverLt = require("semver/functions/lt");
Copyright (c) 2015 - 2018, Buildbotics LLC if (document.getElementById("svelte-dialog-host") != undefined) {
All rights reserved. SvelteComponents.createComponent(
"DialogHost",
This file ("the software") is free software: you can redistribute it document.getElementById("svelte-dialog-host")
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 if (document.getElementById("adminViewSvelte") != undefined) {
along with the software. If not, see <http://www.gnu.org/licenses/>. SvelteComponents.createComponent("AdminNetworkView");
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'
const api = require('./api');
const cookie = require('./cookie')('bbctrl-');
const Sock = require('./sock');
const omit = require('lodash.omit');
function is_newer_version(current, latest) {
const pattern = /(\d+)\.(\d+)\.(\d+)(.*)/;
const currentParts = current.match(pattern);
const latestParts = latest.match(pattern);
if (!currentParts || !latestParts) {
return false;
} }
// Normal version comparisons function parse_version(v) {
const major = latestParts[1] - currentParts[1]; const pattern = /^(\d+)\.(\d+)\.(\d+)(?:[-.]?(.*))?$/;
const minor = latestParts[2] - currentParts[2]; const [ version, major, minor, patch, pre ] = v.trim().match(pattern) || [];
const patch = latestParts[3] - currentParts[3];
// If current is a pre-release, and latest is a release return {
const betaToRelease = latestParts[4].length === 0 && currentParts[4].length > 0; version,
major,
switch (true) { minor,
case major > 0: patch,
case major === 0 && minor > 0: pre
case major === 0 && minor === 0 && patch > 0: };
case major === 0 && minor === 0 && patch === 0 && betaToRelease:
return true;
default:
return false;
}
} }
function is_object(o) { return o !== null && typeof o == 'object' } function fixup_version_number(version) {
function is_array(o) { return Array.isArray(o) } const v = parse_version(version);
version = `${v.major}.${v.minor}.${v.patch}`;
if (v.pre) {
const [ , prefix, num ] = v.pre.match(/([a-zA-Z])(\d+)/);
const suffix = prefix === "b"
? `beta.${num}`
: v.pre;
version = `${version}-${suffix}`;
}
return version;
}
function is_object(o) {
return o !== null && typeof o == "object";
}
function is_array(o) {
return Array.isArray(o);
}
function update_array(dst, src) { function update_array(dst, src) {
while (dst.length) dst.pop() while (dst.length) {
for (var i = 0; i < src.length; i++) dst.pop();
Vue.set(dst, i, src[i]);
} }
for (let i = 0; i < src.length; i++) {
Vue.set(dst, i, src[i]);
}
}
function hasOwnProperty(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function update_object(dst, src, remove) { function update_object(dst, src, remove) {
var props, index, key, value; let props, index, key, value;
if (remove) { if (remove) {
props = Object.getOwnPropertyNames(dst); props = Object.getOwnPropertyNames(dst);
for (index in props) { for (index in props) {
key = props[index]; key = props[index];
if (!src.hasOwnProperty(key)) if (!hasOwnProperty(src, key)) {
Vue.delete(dst, key); Vue.delete(dst, key);
} }
} }
}
props = Object.getOwnPropertyNames(src); props = Object.getOwnPropertyNames(src);
for (index in props) { for (index in props) {
key = props[index]; key = props[index];
value = src[key]; value = src[key];
if (is_array(value) && dst.hasOwnProperty(key) && is_array(dst[key])) if (is_array(value) && hasOwnProperty(dst, key) && is_array(dst[key])) {
update_array(dst[key], value); update_array(dst[key], value);
} else if (is_object(value) && hasOwnProperty(dst, key) && is_object(dst[key])) {
else if (is_object(value) && dst.hasOwnProperty(key) && is_object(dst[key]))
update_object(dst[key], value, remove); update_object(dst[key], value, remove);
} else {
else Vue.set(dst, key, value); Vue.set(dst, key, value);
}
} }
} }
module.exports = new Vue({ module.exports = new Vue({
el: 'body', el: "body",
data: function() { data: function() {
return { return {
status: 'connecting', status: "connecting",
currentView: 'loading', currentView: "loading",
display_units: localStorage.getItem("display_units") || "METRIC",
index: -1, index: -1,
modified: false, modified: false,
template: require('../resources/config-template.json'), template: require("../resources/config-template.json"),
config: { config: {
settings: { units: 'METRIC' }, settings: { units: "METRIC" },
motors: [{}, {}, {}, {}], motors: [{}, {}, {}, {}],
version: '<loading>', version: "<loading>",
full_version: '<loading>' full_version: "<loading>",
ip: "<>",
wifiName: "not connected",
}, },
state: { state: {
messages: [], messages: [],
probing_active: false,
wait_for_probing_complete: false,
show_probe_complete_modal: false,
show_probe_failed_modal: false
}, },
video_size: cookie.get('video-size', 'small'), video_size: cookie.get("video-size", "small"),
crosshair: cookie.get('crosshair', 'false') != 'false', crosshair: cookie.get("crosshair", "false") != "false",
errorTimeout: 30, errorTimeout: 30,
errorTimeoutStart: 0, errorTimeoutStart: 0,
errorShow: false, errorShow: false,
errorMessage: '', errorMessage: "",
confirmUpgrade: false, confirmUpgrade: false,
confirmUpload: false, confirmUpload: false,
firmwareUpgrading: false, firmwareUpgrading: false,
checkedUpgrade: false, checkedUpgrade: false,
firmwareName: '', firmwareName: "",
latestVersion: '', latestVersion: "",
ipAddress: '0.0.0.0', };
wifiSSID: '',
confirmShutdown: false,
diskSpace: ''
}
}, },
components: { components: {
'estop': { template: '#estop-template' }, estop: { template: "#estop-template" },
'loading-view': { template: '<h1>Loading...</h1>' }, "loading-view": { template: "<h1>Loading...</h1>" },
'control-view': require('./control-view'), "control-view": require("./control-view"),
'settings-view': require('./settings-view'), "settings-view": require("./settings-view"),
'motor-view': require('./motor-view'), "motor-view": require("./motor-view"),
'tool-view': require('./tool-view'), "tool-view": require("./tool-view"),
'io-view': require('./io-view'), "io-view": require("./io-view"),
'admin-general-view': require('./admin-general-view'), "admin-general-view": require("./admin-general-view"),
'admin-network-view': require('./admin-network-view'), "admin-network-view": require("./admin-network-view"),
'help-view': { template: '#help-view-template' }, "help-view": require("./help-view"),
'cheat-sheet-view': { "cheat-sheet-view": {
template: '#cheat-sheet-view-template', template: "#cheat-sheet-view-template",
data: function () { return { showUnimplemented: false } } data: function() {
} return {
showUnimplemented: false
};
},
},
},
watch: {
display_units: function(value) {
localStorage.setItem("display_units", value);
SvelteComponents.setDisplayUnits(value);
},
}, },
events: { events: {
'config-changed': function () { "config-changed": function() {
this.modified = true; this.modified = true;
}, },
'hostname-changed': function (hostname) {
this.hostname = hostname
},
send: function(msg) { send: function(msg) {
if (this.status == 'connected') { if (this.status == "connected") {
console.debug('>', msg);
this.sock.send(msg); this.sock.send(msg);
} }
}, },
connected: function() { connected: function() {
this.update() this.update();
}, },
update: function() { update: function() {
this.update() this.update();
}, },
check: function () { check: async function() {
this.latestVersion = ''; try {
const response = await fetch("https://raw.githubusercontent.com/OneFinityCNC/onefinity-release/master/latest.txt", {
cache: "no-cache"
});
$.ajax({ this.latestVersion = (await response.text()).trim();
type: 'GET', } catch (err) {
url: 'https://raw.githubusercontent.com/OneFinityCNC/onefinity-release/master/latest.txt', this.latestVersion = "";
data: { hid: this.state.hid }, }
cache: false
}).done(function (data) {
this.latestVersion = data;
this.$broadcast('latest_version', data);
}.bind(this))
}, },
upgrade: function() { upgrade: function() {
@@ -210,8 +204,9 @@ module.exports = new Vue({
error: function(msg) { error: function(msg) {
// Honor user error blocking // Honor user error blocking
if (Date.now() - this.errorTimeoutStart < this.errorTimeout * 1000) if (Date.now() - this.errorTimeoutStart < this.errorTimeout * 1000) {
return; return;
}
// Wait at least 1 sec to pop up repeated errors // Wait at least 1 sec to pop up repeated errors
if (1 < msg.repeat && Date.now() - msg.ts < 1000) { if (1 < msg.repeat && Date.now() - msg.ts < 1000) {
@@ -221,7 +216,7 @@ module.exports = new Vue({
// Popup error dialog // Popup error dialog
this.errorShow = true; this.errorShow = true;
this.errorMessage = msg.msg; this.errorMessage = msg.msg;
} },
}, },
computed: { computed: {
@@ -236,171 +231,130 @@ module.exports = new Vue({
} }
return msgs; return msgs;
} },
}, },
ready: function() { ready: function() {
$(window).on('hashchange', this.parse_hash); window.onhashchange = () => this.parse_hash();
this.connect(); this.connect();
SvelteComponents.registerControllerMethods({
dispatch: (...args) => this.$dispatch(...args)
});
}, },
methods: { methods: {
metric: function () {
return this.config.settings.units != 'IMPERIAL'
},
block_error_dialog: function() { block_error_dialog: function() {
this.errorTimeoutStart = Date.now(); this.errorTimeoutStart = Date.now();
this.errorShow = false; this.errorShow = false;
}, },
toggle_video: function (e) { toggle_video: function() {
if (this.video_size == 'small') this.video_size = 'large'; if (this.video_size == "small") {
else if (this.video_size == 'large') this.video_size = 'small'; this.video_size = "large";
cookie.set('video-size', this.video_size); } else if (this.video_size == "large") {
this.video_size = "small";
}
cookie.set("video-size", this.video_size);
}, },
toggle_crosshair: function(e) { toggle_crosshair: function(e) {
e.preventDefault(); e.preventDefault();
this.crosshair = !this.crosshair; this.crosshair = !this.crosshair;
cookie.set('crosshair', this.crosshair); cookie.set("crosshair", this.crosshair);
}, },
estop: function() { estop: function() {
if (this.state.xx == 'ESTOPPED') api.put('clear'); if (this.state.xx == "ESTOPPED") {
else api.put('estop'); api.put("clear");
} else {
api.put("estop");
}
}, },
upgrade_confirmed: async function() { upgrade_confirmed: async function() {
this.confirmUpgrade = false; this.confirmUpgrade = false;
try { try {
await api.put('upgrade'); await api.put("upgrade");
this.firmwareUpgrading = true; this.firmwareUpgrading = true;
} catch (err) { } catch (error) {
api.alert('Error during upgrade.'); console.error("Error during upgrade:", error);
console.error("Error during upgrade", err); alert("Error during upgrade");
} }
}, },
upload_confirmed: function () { upload_confirmed: async function() {
this.confirmUpload = false; this.confirmUpload = false;
const form = new FormData(); const form = new FormData();
form.append('firmware', this.firmware); form.append("firmware", this.firmware);
$.ajax({ try {
url: '/api/firmware/update', await api.put("firmware/update", form);
type: 'PUT',
data: form,
cache: false,
contentType: false,
processData: false
}).success(function () {
this.firmwareUpgrading = true; this.firmwareUpgrading = true;
}.bind(this)).error(function (err) { } catch (error) {
api.alert('Firmware update failed'); console.error("Firmware update failed:", error);
console.error('Firmware update failed', err); alert("Firmware update failed");
}.bind(this)) }
}, },
show_upgrade: function() { show_upgrade: function() {
if (!this.latestVersion) return false; if (!this.latestVersion) {
return is_newer_version(this.config.version, this.latestVersion); return false;
}
return semverLt(this.config.full_version, this.latestVersion);
}, },
update: function () { showShutdownDialog: function() {
api.get('config/load').done(function (config) { SvelteComponents.showDialog("Shutdown");
},
update: async function() {
const config = await api.get("config/load");
const wifi = await api.get("wifi");
update_object(this.config, config, true); update_object(this.config, config, true);
this.config.full_version = fixup_version_number(this.config.full_version);
this.config.ip = wifi.ipAddresses;
this.config.wifiName = wifi.wifi;
this.parse_hash(); this.parse_hash();
if (!this.checkedUpgrade) { if (!this.checkedUpgrade) {
this.checkedUpgrade = true; this.checkedUpgrade = true;
var check = this.config.admin['auto-check-upgrade']; const check = this.config.admin["auto-check-upgrade"];
if (typeof check == 'undefined' || check) if (typeof check == "undefined" || check) {
this.$emit('check'); this.$emit("check");
}
} }
this.check_ip_address(); SvelteComponents.handleConfigUpdate(this.config);
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 = 'IP:' + 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 = 'SSID:' + 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;
},
shutdown: function () {
this.confirmShutdown = false;
api.put('shutdown');
},
reboot: function () {
this.confirmShutdown = false;
api.put('reboot');
}, },
connect: function() { connect: function() {
this.sock = new Sock(`//${location.host}/sockjs`); this.sock = new Sock(`//${location.host}/sockjs`);
this.sock.onmessage = (e) => { this.sock.onmessage = (e) => {
if (typeof e.data != 'object') { if (typeof e.data != "object") {
return; return;
} }
if ('log' in e.data) { if (e.data.log && e.data.log.msg !== "Switch not found") {
if (e.data.log.msg === "Switch not found") { this.$broadcast("log", e.data.log);
this.$broadcast('probing_failed');
} else {
this.$broadcast('log', e.data.log);
}
delete e.data.log; if (Object.keys(e.data).length === 1) {
// If there's only log data, we're done
return;
}
} }
// Check for session ID change on controller // Check for session ID change on controller
if ('sid' in e.data) { if ("sid" in e.data) {
if (typeof this.sid == 'undefined') { if (typeof this.sid == "undefined") {
this.sid = e.data.sid; this.sid = e.data.sid;
} else if (this.sid != e.data.sid) { } else if (this.sid != e.data.sid) {
if (typeof this.hostname !== 'undefined' && location.hostname !== 'localhost') { if (this.hostname && location.hostname !== "localhost") {
location.hostname = this.hostname; location.hostname = this.hostname;
} }
@@ -408,99 +362,85 @@ module.exports = new Vue({
} }
} }
// Set this to true to get console output of changes to the state
const debugStateChanges = false;
if (debugStateChanges) {
const data = omit(e.data, [
'vdd',
'vin',
'vout',
'motor',
'temp',
'heartbeat',
'load1',
'load2',
'rpi_temp'
]);
if (Object.keys(data).length > 0) {
console.log(JSON.stringify(data, null, 4));
}
}
update_object(this.state, e.data, false); update_object(this.state, e.data, false);
if (this.state.pw === 0) { SvelteComponents.handleControllerStateUpdate(this.state);
Vue.set(this.state, "saw_probe_connected", true);
}
if (this.state.cycle === 'idle') { delete this.state.log;
if (this.state.wait_for_probing_complete) {
Vue.set(this.state, "wait_for_probing_complete", false);
this.$broadcast("probing_complete");
}
}
this.$broadcast('update'); this.$broadcast("update");
}; };
this.sock.onopen = () => { this.sock.onopen = () => {
this.status = 'connected'; this.status = "connected";
this.$emit(this.status); this.$emit(this.status);
this.$broadcast(this.status); this.$broadcast(this.status);
}; };
this.sock.onclose = () => { this.sock.onclose = () => {
this.status = 'disconnected'; this.status = "disconnected";
this.$emit(this.status); this.$emit(this.status);
this.$broadcast(this.status); this.$broadcast(this.status);
}; };
}, },
parse_hash: function() { parse_hash: function() {
var hash = location.hash.substr(1); const hash = location.hash.substr(1);
if (!hash.trim().length) { if (!hash.trim().length) {
location.hash = 'control'; location.hash = "control";
return; return;
} }
var parts = hash.split(':'); const parts = hash.split(":");
if (parts.length == 2) this.index = parts[1]; if (parts.length == 2) {
this.index = parts[1];
}
this.currentView = parts[0]; this.currentView = parts[0];
}, },
save: function () { save: async function() {
const selected_tool = this.config.tool['selected-tool']; const selected_tool = this.config.tool["selected-tool"];
const saveModbus = selected_tool !== "pwm" && const saveModbus =
selected_tool !== "pwm" &&
selected_tool !== "laser" && selected_tool !== "laser" &&
selected_tool !== "router"; selected_tool !== "router";
const settings = { const settings = {
['tool']: { ...this.config.tool }, ["tool"]: { ...this.config.tool },
['pwm-spindle']: { ...this.config['pwm-spindle'] }, ["pwm-spindle"]: { ...this.config["pwm-spindle"] },
['modbus-spindle']: saveModbus ? { ...this.config['modbus-spindle'] } : undefined ["modbus-spindle"]: saveModbus
} ? { ...this.config["modbus-spindle"] }
delete settings.tool['tool-type']; : undefined,
};
delete settings.tool["tool-type"];
this.config['selected-tool-settings'][selected_tool] = settings; this.config["selected-tool-settings"][selected_tool] = settings;
api.put('config/save', this.config).done(function (data) { try {
await api.put("config/save", this.config);
this.modified = false; this.modified = false;
}.bind(this)).fail(function (error) { } catch (error) {
api.alert('Save failed', error); console.error("Save failed:", error);
}); alert("Save failed");
}
}, },
close_messages: function(action) { close_messages: function(action) {
if (action == 'stop') api.put('stop'); if (action == "stop") {
if (action == 'continue') api.put('unpause'); api.put("stop");
}
if (action == "continue") {
api.put("unpause");
}
// Acknowledge messages // Acknowledge messages
if (this.state.messages.length) { if (this.state.messages.length) {
var id = this.state.messages.slice(-1)[0].id const id = this.state.messages.slice(-1)[0].id;
api.put('message/' + id + '/ack'); api.put(`message/${id}/ack`);
} }
} },
} },
}) });

View File

@@ -1,65 +1,37 @@
/******************************************************************************\ "use strict";
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 = { module.exports = {
template: '#axis-control-template', template: "#axis-control-template",
props: ['axes', 'colors', 'enabled', 'adjust', 'step'], props: [ "axes", "colors", "enabled", "adjust", "step" ],
methods: { methods: {
jog: function(axis, ring, direction) { jog: function(axis, ring, direction) {
var value = direction * this.value(ring); const value = direction * this.value(ring);
this.$dispatch(this.step ? 'step' : 'jog', this.axes[axis], value); this.$dispatch(this.step ? "step" : "jog", this.axes[axis], value);
}, },
back2zero: function(axis0,axis1) { back2zero: function(axis0,axis1) {
this.$dispatch('back2zero',this.axes[axis0],this.axes[axis1]) this.$dispatch("back2zero",this.axes[axis0],this.axes[axis1]);
}, },
release: function(axis) { release: function(axis) {
if (!this.step) this.$dispatch('jog', this.axes[axis], 0) if (!this.step) {
this.$dispatch("jog", this.axes[axis], 0);
}
}, },
value: function(ring) { value: function(ring) {
var adjust = [0.01, 0.1, 1][this.adjust]; const adjust = [ 0.01, 0.1, 1 ][this.adjust];
if (this.step) return adjust * [0.1, 1, 10, 100][ring]; if (this.step) {
return adjust * [ 0.1, 1, 10, 100 ][ring];
}
return adjust * [ 0.1, 0.25, 0.5, 1 ][ring]; return adjust * [ 0.1, 0.25, 0.5, 1 ][ring];
}, },
text: function(ring) { text: function(ring) {
var value = this.value(ring) * (this.step ? 1 : 100); let value = this.value(ring) * (this.step ? 1 : 100);
value = parseFloat(value.toFixed(3)); value = parseFloat(value.toFixed(3));
return value + (this.step ? '' : '%'); return value + (this.step ? "" : "%");
}
} }
} }
};

View File

@@ -1,159 +1,162 @@
/******************************************************************************\ "use strict";
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 = { module.exports = {
props: ['state', 'config'], props: [ "state", "config" ],
computed: { computed: {
x: function () {return this._compute_axis('x')}, metric: function() {
y: function () {return this._compute_axis('y')}, return this.$root.display_units === "METRIC";
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()}
}, },
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: { methods: {
_convert_length: function(value) { _convert_length: function(value) {
return this.state.imperial ? value / 25.4 : value; return this.metric
? value
: value / 25.4;
}, },
_length_str: function(value) { _length_str: function(value) {
return this._convert_length(value).toLocaleString() + return this._convert_length(value).toLocaleString() + (this.metric ? " mm" : " in");
(this.state.imperial ? ' in' : ' mm');
}, },
_compute_axis: function(axis) { _compute_axis: function(axis) {
var abs = this.state[axis + 'p'] || 0; const abs = this.state[`${axis}p`] || 0;
var off = this.state['offset_' + axis]; const off = this.state[`offset_${axis}`];
var motor_id = this._get_motor_id(axis); const motor_id = this._get_motor_id(axis);
var motor = motor_id == -1 ? {} : this.config.motors[motor_id]; const motor = motor_id == -1 ? {} : this.config.motors[motor_id];
var enabled = typeof motor.enabled != 'undefined' && motor.enabled; const enabled = typeof motor.enabled != "undefined" && motor.enabled;
var homingMode = motor['homing-mode'] const homingMode = motor["homing-mode"];
var homed = this.state[motor_id + 'homed']; const homed = this.state[`${motor_id}homed`];
var min = this.state[motor_id + 'tn']; const min = this.state[`${motor_id}tn`];
var max = this.state[motor_id + 'tm']; const max = this.state[`${motor_id}tm`];
var dim = max - min; const dim = max - min;
var pathMin = this.state['path_min_' + axis]; const pathMin = this.state[`path_min_${axis}`];
var pathMax = this.state['path_max_' + axis]; const pathMax = this.state[`path_max_${axis}`];
var pathDim = pathMax - pathMin; const pathDim = pathMax - pathMin;
var under = pathMin + off < min; const under = pathMin + off < min;
var over = max < pathMax + off; const over = max < pathMax + off;
var klass = (homed ? 'homed' : 'unhomed') + ' axis-' + axis; let klass = `${homed ? "homed" : "unhomed"} axis-${axis}`;
var state = 'UNHOMED'; let state = "UNHOMED";
var icon = 'question-circle'; let icon = "question-circle";
var fault = this.state[motor_id + 'df'] & 0x1f; const fault = this.state[`${motor_id}df`] & 0x1f;
var shutdown = this.state.power_shutdown; const shutdown = this.state.power_shutdown;
var title; let title;
var ticon = 'question-circle'; let ticon = "question-circle";
var tstate = 'NO FILE'; let tstate = "NO FILE";
var toolmsg; let toolmsg;
var tklass = (homed ? 'homed' : 'unhomed') + ' axis-' + axis; let tklass = `${homed ? "homed" : "unhomed"} axis-${axis}`;
if (fault || shutdown) { if (fault || shutdown) {
state = shutdown ? 'SHUTDOWN' : 'FAULT'; state = shutdown ? "SHUTDOWN" : "FAULT";
klass += ' error'; klass += " error";
icon = 'exclamation-circle'; icon = "exclamation-circle";
} else if (homed) { } else if (homed) {
state = 'HOMED'; state = "HOMED";
icon = 'check-circle'; icon = "check-circle";
} }
if (0 < dim && dim < pathDim) { if (0 < dim && dim < pathDim) {
tstate = 'NO FIT'; tstate = "NO FIT";
tklass += ' error'; tklass += " error";
ticon = 'ban'; ticon = "ban";
} else { } else {
if (over || under) { if (over || under) {
tstate = over ? 'OVER' : 'UNDER'; tstate = over ? "OVER" : "UNDER";
tklass += ' warn'; tklass += " warn";
ticon = 'exclamation-circle'; ticon = "exclamation-circle";
} else { } else {
tstate = 'OK'; tstate = "OK";
ticon = 'check-circle'; ticon = "check-circle";
} }
} }
switch (state) { switch (state) {
case 'UNHOMED': title = 'Click the home button to home axis.'; break; case "UNHOMED":
case 'HOMED': title = 'Axis successfuly homed.'; break; title = "Click the home button to home axis.";
case 'FAULT': break;
title = 'Motor driver fault. A potentially damaging electrical ' +
'condition was detected and the motor driver was shutdown. ' + case "HOMED":
'Please power down the controller and check your motor cabling. ' + title = "Axis successfuly homed.";
'See the "Motor Faults" table on the "Indicators" tab for more ' + break;
'information.';
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.`,
].join(" ");
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.`
].join(" ");
break; 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.';
} }
switch (tstate) { switch (tstate) {
case "OVER":
case 'OVER': toolmsg = [
toolmsg = 'Caution: The current tool path file would move ' + `Caution: The current tool path file would move`,
this._length_str(pathMax + off - max) + ' above axis limit with the current offset.'; `${this._length_str(pathMax + off - max)}`,
`above axis limit with the current offset.`
].join(" ");
break; break;
case 'UNDER': case "UNDER":
toolmsg = 'Caution: The current tool path file would move ' + toolmsg = [
this._length_str(min - pathMin - off) + ' below limit with the current offset.'; `Caution: The current tool path file would move`,
`${this._length_str(min - pathMin - off)}`,
`below limit with the current offset.`
].join(" ");
break; break;
case 'NO FIT': case "NO FIT":
toolmsg = 'Warning: The current tool path dimensions (' + toolmsg = [
this._length_str(pathDim) + ') exceed axis dimensions (' + `Warning: The current tool path dimensions`,
this._length_str(dim) + ') by ' + `(${this._length_str(pathDim)}) exceed axis dimensions`,
this._length_str(pathDim - dim) + '.'; `(${this._length_str(dim)}) by ${this._length_str(pathDim - dim)}.`
].join(" ");
break; break;
default: default:
toolmsg = 'Tool path ' + axis + ' dimensions OK.'; toolmsg = `Tool path ${axis} dimensions OK.`;
break; break;
} }
return { return {
pos: abs - off, pos: abs - off,
abs: abs, abs: abs,
@@ -176,57 +179,76 @@ module.exports = {
tstate: tstate, tstate: tstate,
toolmsg: toolmsg, toolmsg: toolmsg,
tklass: tklass tklass: tklass
} };
}, },
_get_motor_id: function(axis) { _get_motor_id: function(axis) {
for (var i = 0; i < this.config.motors.length; i++) { for (let i = 0; i < this.config.motors.length; i++) {
var motor = this.config.motors[i]; const motor = this.config.motors[i];
if (motor.axis.toLowerCase() == axis) return i; if (motor.axis.toLowerCase() == axis) {
return i;
}
} }
return -1; return -1;
}, },
_compute_axes: function() { _compute_axes: function() {
var homed = false; let homed = false;
for (var name of 'xyzabc') { for (const name of "xyzabc") {
var axis = this[name]; const axis = this[name];
if (!axis.enabled) {
continue;
}
if (!axis.homed) {
homed = false; break;
}
if (!axis.enabled) continue
if (!axis.homed) {homed = false; break}
homed = true; homed = true;
} }
var error = false; let error = false;
var warn = false; let warn = false;
if (homed) if (homed) {
for (name of 'xyzabc') { for (const name of "xyzabc") {
axis = this[name]; const axis = this[name];
if (!axis.enabled) continue; if (!axis.enabled) {
if (axis.klass.indexOf('error') != -1) error = true; continue;
if (axis.klass.indexOf('warn') != -1) warn = true;
} }
var klass = homed ? 'homed' : 'unhomed'; if (axis.klass.indexOf("error") != -1) {
if (error) klass += ' error'; error = true;
else if (warn) klass += ' warn'; }
if(!homed && this.ask_home) if (axis.klass.indexOf("warn") != -1) {
{ warn = true;
this.ask_home_msg = true; }
}
}
let klass = homed ? "homed" : "unhomed";
if (error) {
klass += " error";
} else if (warn) {
klass += " warn";
}
if (!homed && this.ask_home) {
this.ask_home = false; this.ask_home = false;
SvelteComponents.showDialog("HomeMachine", {
home: () => this.home()
});
} }
return { return {
homed: homed, homed: homed,
klass: klass klass: klass
};
} }
} }
} };
}

View File

@@ -1,87 +1,77 @@
/******************************************************************************\ "use strict";
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) { function _msg_equal(a, b) {
return a.level == b.level && a.source == b.source && a.where == b.where && return a.level == b.level
a.msg == b.msg; && a.source == b.source
&& a.where == b.where
&& a.msg == b.msg;
} }
// Shared among all instances // Shared among all instances
var messages = []; const messages = [];
module.exports = { module.exports = {
template: '#console-template', template: "#console-template",
data: function() { data: function() {
return {messages: messages} return {
messages
};
}, },
events: { events: {
log: function(msg) { log: function(msg) {
// There may be multiple instances of this module so ignore messages // There may be multiple instances of this module so ignore messages
// that have already been processed. // that have already been processed.
if (msg.logged) return; if (msg.logged) {
return;
}
msg.logged = true; msg.logged = true;
// Make sure we have a message level // Make sure we have a message level
msg.level = msg.level || 'info'; msg.level = msg.level || "info";
// Add to message log and count and collapse repeats // Add to message log and count and collapse repeats
var repeat = messages.length && _msg_equal(msg, messages[0]); const repeat = messages.length && _msg_equal(msg, messages[0]);
if (repeat) messages[0].repeat++; if (repeat) {
else { messages[0].repeat++;
} else {
msg.repeat = msg.repeat || 1; msg.repeat = msg.repeat || 1;
messages.unshift(msg); messages.unshift(msg);
while (256 < messages.length) messages.pop(); while (256 < messages.length) {
messages.pop();
} }
}
if (messages[0].repeat > 1) {
return;
}
msg.ts = Date.now(); msg.ts = Date.now();
// Write message to browser console for debugging // Write message to browser console for debugging
var text = JSON.stringify(msg); const text = JSON.stringify(msg);
if (msg.level == 'error' || msg.level == 'critical') console.error(text); if (msg.level == "error" || msg.level == "critical") {
else if (msg.level == 'warning') console.warn(text); console.error(text);
else if (msg.level == 'debug' && console.debug) console.debug(text); } else if (msg.level == "warning") {
else console.log(text); console.warn(text);
} else if (msg.level == "debug" && console.debug) {
console.debug(text);
} else {
console.log(text);
}
// Event on errors // Event on errors
if (msg.level == 'error' || msg.level == 'critical') if (msg.level == "error" || msg.level == "critical") {
this.$dispatch('error', msg); this.$dispatch("error", msg);
}
} }
}, },
methods: { methods: {
clear: function () {messages.splice(0, messages.length);}, clear: function() {
} messages.splice(0, messages.length);
},
} }
};

View File

@@ -1,515 +1,294 @@
/******************************************************************************\ "use strict";
This file is part of the Buildbotics firmware. const api = require("./api");
const utils = require("./utils");
Copyright (c) 2015 - 2018, Buildbotics LLC const cookie = require("./cookie")("bbctrl-");
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-');
module.exports = { module.exports = {
template: '#control-view-template', template: "#control-view-template",
props: ['config', 'template', 'state'], props: [ "config", "template", "state" ],
data: function() { data: function() {
return { return {
mach_units: 'METRIC', current_time: "",
mdi: '', mach_units: this.$root.state.metric ? "METRIC" : "IMPERIAL",
mdi: "",
last_file: undefined, last_file: undefined,
last_file_time: undefined, last_file_time: undefined,
toolpath: {}, toolpath: {},
toolpath_progress: 0, toolpath_progress: 0,
axes: 'xyzabc', axes: "xyzabc",
history: [], history: [],
speed_override: 1, speed_override: 1,
feed_override: 1, feed_override: 1,
manual_home: { jog_incr_amounts: {
x: false, "METRIC": {
y: false, fine: 0.1,
z: false, small: 1.0,
a: false, medium: 10,
b: false, large: 100,
c: false
}, },
position_msg: { "IMPERIAL": {
x: false, fine: 0.005,
y: false, small: 0.05,
z: false, medium: 0.5,
a: false, large: 5,
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_diameter: 6.35,
tool_diameter_for_prompt: 6.35,
show_probe_test_modal: false,
show_tool_diameter_modal: false,
toolpath_msg: {
x: false,
y: false,
z: false,
a: false,
b: false,
c: false
},
ask_home: true,
ask_home_msg: false,
ask_zero_xy_msg: false,
ask_zero_z_msg: false,
showGcodeMessage: false
} }
}, },
jog_incr: localStorage.getItem("jog_incr") || "small",
jog_step: cookie.get_bool("jog-step"),
jog_adjust: parseInt(cookie.get("jog-adjust", 2)),
deleteGCode: false,
tab: "auto",
ask_home: true,
showGcodeMessage: false
};
},
components: { components: {
'axis-control': require('./axis-control'), "axis-control": require("./axis-control"),
'path-viewer': require('./path-viewer'), "path-viewer": require("./path-viewer"),
'gcode-viewer': require('./gcode-viewer') "gcode-viewer": require("./gcode-viewer")
}, },
watch: { watch: {
'state.imperial': { jog_incr: function(value) {
handler: function (imperial) { localStorage.setItem("jog_incr", value);
this.mach_units = imperial ? 'IMPERIAL' : 'METRIC'; },
"state.metric": {
handler: function(metric) {
this.mach_units = metric
? "METRIC"
: "IMPERIAL";
}, },
immediate: true immediate: true
}, },
'state.bitDiameter': { "state.line": function() {
handler: function (bitDiameter) { if (this.mach_state != "HOMING") {
this.tool_diameter = bitDiameter; this.$broadcast("gcode-line", this.state.line);
}, }
immediate: true
}, },
"state.selected_time": function() {
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(); this.load();
}, },
jog_step: function() { jog_step: function() {
cookie.set_bool('jog-step', this.jog_step); cookie.set_bool("jog-step", this.jog_step);
}, },
jog_adjust: function() { jog_adjust: function() {
cookie.set('jog-adjust', this.jog_adjust); cookie.set("jog-adjust", this.jog_adjust);
} }
}, },
computed: { computed: {
metric: function () { display_units: {
return !this.state.imperial; cache: false,
get: function() {
return this.$root.display_units;
},
set: function(value) {
this.$root.display_units = value;
}
}, },
metric: function() {
return this.display_units === "METRIC";
},
mach_state: function() { mach_state: function() {
var cycle = this.state.cycle; const cycle = this.state.cycle;
var state = this.state.xx; const state = this.state.xx;
if (typeof cycle != 'undefined' && state != 'ESTOPPED' && if (state != "ESTOPPED" && (cycle == "jogging" || cycle == "homing")) {
(cycle == 'jogging' || cycle == 'homing'))
return cycle.toUpperCase(); return cycle.toUpperCase();
return state || '' }
return state || "";
}, },
pause_reason: function() {
pause_reason: function () {return this.state.pr}, return this.state.pr;
},
is_running: function() { is_running: function() {
return this.mach_state == 'RUNNING' || this.mach_state == 'HOMING'; return this.mach_state == "RUNNING" || this.mach_state == "HOMING";
}, },
is_stopping: function() {
return this.mach_state == "STOPPING";
},
is_stopping: function () {return this.mach_state == 'STOPPING'}, is_holding: function() {
is_holding: function () {return this.mach_state == 'HOLDING'}, return this.mach_state == "HOLDING";
is_ready: function () {return this.mach_state == 'READY'}, },
is_idle: function () {return this.state.cycle == 'idle'},
is_ready: function() {
return this.mach_state == "READY";
},
is_idle: function() {
return this.state.cycle == "idle";
},
is_paused: function() { is_paused: function() {
return this.is_holding && return this.is_holding && (this.pause_reason == "User pause" || this.pause_reason == "Program pause");
(this.pause_reason == 'User pause' ||
this.pause_reason == 'Program pause')
}, },
can_mdi: function() {
can_mdi: function () {return this.is_idle || this.state.cycle == 'mdi'}, return this.is_idle || this.state.cycle == "mdi";
},
can_set_axis: function() { can_set_axis: function() {
return this.is_idle return this.is_idle;
// TODO allow setting axis position during pause
return this.is_idle || this.is_paused
},
// TODO allow setting axis position during pause
// return this.is_idle || this.is_paused;
},
message: function() { message: function() {
if (this.mach_state == 'ESTOPPED') return this.state.er; if (this.mach_state == "ESTOPPED") {
if (this.mach_state == 'HOLDING') return this.state.pr; return this.state.er;
if (this.state.messages.length) }
return this.state.messages.slice(-1)[0].text;
return '';
},
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() { highlight_state: function() {
return this.mach_state == 'ESTOPPED' || this.mach_state == 'HOLDING'; return this.mach_state == "ESTOPPED" || this.mach_state == "HOLDING";
}, },
plan_time: function() {
plan_time: function () {return this.state.plan_time}, return this.state.plan_time;
},
plan_time_remaining: function() { plan_time_remaining: function() {
if (!(this.is_stopping || this.is_running || this.is_holding)) return 0; if (!(this.is_stopping || this.is_running || this.is_holding)) {
return this.toolpath.time - this.plan_time return 0;
}
return this.toolpath.time - this.plan_time;
}, },
eta: function() { eta: function() {
if (this.mach_state != 'RUNNING') return ''; if (this.mach_state != "RUNNING") {
var remaining = this.plan_time_remaining; return "";
var d = new Date(); }
const remaining = this.plan_time_remaining;
const d = new Date();
d.setSeconds(d.getSeconds() + remaining); d.setSeconds(d.getSeconds() + remaining);
return d.toLocaleString(); return d.toLocaleString();
}, },
progress: function() { progress: function() {
if (!this.toolpath.time || this.is_ready) return 0; if (!this.toolpath.time || this.is_ready) {
var p = this.plan_time / this.toolpath.time; return 0;
return p < 1 ? p : 1; }
const p = this.plan_time / this.toolpath.time;
return Math.min(1, p);
} }
}, },
events: { events: {
jog: function(axis, power) { jog: function(axis, power) {
var data = {ts: new Date().getTime()}; const data = { ts: new Date().getTime() };
data[axis] = power; data[axis] = power;
api.put('jog', data); api.put("jog", data);
}, },
back2zero: function(axis0, axis1) { back2zero: function(axis0, axis1) {
this.send("G0"+axis0+"0"+axis1+"0"); this.send(`G0 ${axis0}0 ${axis1}0`);
}, },
step: function(axis, value) { step: function(axis, value) {
this.send('M70\nG91\nG0' + axis + value + '\nM72'); this.send(`
M70
G91
G0 ${axis}${value}
M72
`);
}, },
probing_failed: function() {
Vue.set(this.state, "probing_active", false);
Vue.set(this.state, "wait_for_probing_complete", false);
Vue.set(this.state, "show_probe_complete_modal", false);
Vue.set(this.state, "goto_xy_zero_after_probe", false);
Vue.set(this.state, "show_probe_failed_modal", true);
}, },
probing_complete: function() {
Vue.set(this.state, "probing_active", false);
if (this.config.settings['probing-prompts']) {
Vue.set(this.state, "show_probe_complete_modal", true);
} else {
this.$emit("finalize_probe");
}
},
finalize_probe: function() {
Vue.set(this.state, "show_probe_complete_modal", false);
if (this.state.goto_xy_zero_after_probe) {
this.goto_zero(1, 1, 0, 0);
}
Vue.set(this.state, "goto_xy_zero_after_probe", false);
}
},
ready: function() { ready: function() {
this.load() this.load();
},
setInterval(() => {
this.current_time = new Date().toLocaleTimeString();
}, 1000);
SvelteComponents.registerControllerMethods({
stop: (...args) => this.stop(...args),
send: (...args) => this.send(...args),
isAxisHomed: (axis) => this[axis].homed,
unhome: (...args) => this.unhome(...args),
set_position: (...args) => this.set_position(...args),
set_home: (...args) => this.set_home(...args)
});
},
methods: { methods: {
units_changed : function() { getJogIncrStyle(value) {
if(this.mach_units == 'METRIC') { const weight = `font-weight:${this.jog_incr === value ? "bold" : "normal"}`;
document.getElementById("jog_button_fine").innerHTML = "0.1"; const color = this.jog_incr === value ? "color:#0078e7" : "";
document.getElementById("jog_button_small").innerHTML = "1.0";
document.getElementById("jog_button_medium").innerHTML = "10";
document.getElementById("jog_button_large").innerHTML = "100";
} 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.set_jog_incr('small'); return [ weight, color ].join(";");
},
start_probe_test: function(on_finish) {
if (!this.config.settings['probing-prompts']) {
on_finish();
return;
}
this.show_probe_test_modal = true;
Vue.set(this.state, "saw_probe_connected", false);
Vue.set(this.state, "on_probe_finish", on_finish);
},
finish_probe_test: function() {
this.show_probe_test_modal = false;
Vue.set(this.state, "saw_probe_connected", false);
const on_finish = this.state.on_probe_finish;
Vue.set(this.state, "on_probe_finish", undefined);
on_finish();
},
hide_probe_failed_modal: function() {
Vue.set(this.state, "show_probe_failed_modal", false);
},
prep_and_show_tool_diameter_modal() {
this.tool_diameter_for_prompt = (this.mach_units == 'METRIC')
? this.tool_diameter
: this.tool_diameter / 25.4;
this.tool_diameter_for_prompt = this.tool_diameter_for_prompt.toFixed(3).replace(/0+$/, "");
this.show_tool_diameter_modal = true;
},
set_tool_diameter() {
this.tool_diameter = parseFloat(this.tool_diameter_for_prompt);
if (!isFinite(this.tool_diameter)) {
return;
}
this.show_tool_diameter_modal = false;
if (this.mach_units !== "METRIC") {
this.tool_diameter *= 25.4;
}
this.probe_xyz();
},
probe(zOnly = false) {
const xdim = this.config.probe["probe-xdim"];
const ydim = this.config.probe["probe-ydim"];
const zdim = this.config.probe["probe-zdim"];
const slowSeek = this.config.probe["probe-slow-seek"];
const fastSeek = this.config.probe["probe-fast-seek"];
const zlift = 1;
const xoffset = xdim + (this.tool_diameter / 2.0);
const yoffset = ydim + (this.tool_diameter / 2.0);
const zoffset = zdim;
const metric = this.mach_units == "METRIC";
const mm = n => (metric ? n : n / 25.4).toFixed(5);
const speed = s => `F${mm(s)}`;
// After probing Z, we want to drop the bit down:
// Ideally, 12.7mm/0.5in
// And we don't want to be more than 75% down on the probe block
// Also, add zlift to compensate for the fact that we lift after probing Z
const plunge = Math.min(12.7, zoffset * 0.75) + zlift;
Vue.set(this.state, "probing_active", true);
Vue.set(this.state, "goto_xy_zero_after_probe", !zOnly);
if (zOnly) {
this.send(`
${metric ? "G21" : "G20"}
G92 Z0
G38.2 Z ${mm(-25.4)} ${speed(fastSeek)}
G91 G1 Z ${mm(1)}
G38.2 Z ${mm(-2)} ${speed(slowSeek)}
G92 Z ${mm(zoffset)}
G91 G0 Z ${mm(3)}
M2
`);
} else {
this.send(`
${metric ? "G21" : "G20"}
G92 X0 Y0 Z0
G38.2 Z ${mm(-25.4)} ${speed(fastSeek)}
G91 G1 Z ${mm(1)}
G38.2 Z ${mm(-2)} ${speed(slowSeek)}
G92 Z ${mm(zoffset)}
G91 G0 Z ${mm(zlift)}
G91 G0 X ${mm(20)}
G91 G0 Z ${mm(-plunge)}
G38.2 X ${mm(-20)} ${speed(fastSeek)}
G91 G1 X ${mm(1)}
G38.2 X ${mm(-2)} ${speed(slowSeek)}
G92 X ${mm(xoffset)}
G91 G0 X ${mm(1)}
G91 G0 Y ${mm(20)}
G91 G0 X ${mm(-20)}
G38.2 Y ${mm(-20)} ${speed(fastSeek)}
G91 G1 Y ${mm(1)}
G38.2 Y ${mm(-2)} ${speed(slowSeek)}
G92 Y ${mm(yoffset)}
G91 G0 Y ${mm(3)}
G91 G0 Z ${mm(25.4)}
M2
`);
}
// Wait 1 second to let the probing sequence begin,
// then wait for probing to be complete
setTimeout(() => Vue.set(this.state, "wait_for_probing_complete", true), 1000);
},
probe_xyz() {
this.probe(false);
},
probe_z() {
this.probe(true);
},
set_jog_incr: function(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';
this.jog_incr = (this.mach_units == 'METRIC')
? 100
: 5;
}
},
goto_zero(zero_x,zero_y,zero_z,zero_a) {
var xcmd = "";
var ycmd = "";
var zcmd = "";
var acmd = "";
if(zero_x) xcmd = "X0";
if(zero_y) ycmd = "Y0";
if(zero_z) zcmd = "Z0";
if(zero_a) acmd = "A0";
this.ask_zero_xy_msg = false;
this.ask_zero_z_msg = false;
this.send('G90\nG0' + xcmd + ycmd + zcmd + acmd + '\n');
}, },
jog_fn: function(x_jog, y_jog, z_jog, a_jog) { jog_fn: function(x_jog, y_jog, z_jog, a_jog) {
var xcmd = "X" + x_jog * this.jog_incr; const amount = this.jog_incr_amounts[this.display_units][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;
this.send('G91\nG0' + xcmd + ycmd + zcmd + acmd + '\n'); const xcmd = `X${x_jog * amount}`;
const ycmd = `Y${y_jog * amount}`;
const zcmd = `Z${z_jog * amount}`;
const acmd = `A${a_jog * amount}`;
this.send(`
G91
${this.metric ? "G21" : "G20"}
G0 ${xcmd}${ycmd}${zcmd}${acmd}
`);
}, },
send: function(msg) { send: function(msg) {
this.$dispatch('send', msg) this.$dispatch("send", msg);
}, },
load: function() { load: function() {
var file_time = this.state.selected_time; const file_time = this.state.selected_time;
var file = this.state.selected; const file = this.state.selected;
if (this.last_file == file && this.last_file_time == file_time) return; if (this.last_file == file && this.last_file_time == file_time) {
return;
}
this.last_file = file; this.last_file = file;
this.last_file_time = file_time; this.last_file_time = file_time;
this.$broadcast('gcode-load', file); this.$broadcast("gcode-load", file);
this.$broadcast('gcode-line', this.state.line); this.$broadcast("gcode-line", this.state.line);
this.toolpath_progress = 0; this.toolpath_progress = 0;
this.load_toolpath(file, file_time); this.load_toolpath(file, file_time);
}, },
load_toolpath: async function(file, file_time) { load_toolpath: async function(file, file_time) {
this.toolpath = {}; this.toolpath = {};
if (!file) return; if (!file || this.last_file_time != file_time) {
if (this.last_file_time != file_time) return; return;
}
this.showGcodeMessage = true; this.showGcodeMessage = true;
@@ -517,8 +296,8 @@ module.exports = {
const toolpath = await api.get(`path/${file}`); const toolpath = await api.get(`path/${file}`);
this.toolpath_progress = toolpath.progress; this.toolpath_progress = toolpath.progress;
if (toolpath.progress === 1 || typeof toolpath.progress == 'undefined') { if (toolpath.progress === 1 || typeof toolpath.progress == "undefined") {
this.showGcodeMessage = false this.showGcodeMessage = false;
if (toolpath.bounds) { if (toolpath.bounds) {
toolpath.filename = file; toolpath.filename = file;
@@ -526,55 +305,50 @@ module.exports = {
this.toolpath = toolpath; this.toolpath = toolpath;
const state = this.$root.state; const state = this.$root.state;
for (let axis of 'xyzabc') { for (const axis of "xyzabc") {
Vue.set(state, 'path_min_' + axis, toolpath.bounds.min[axis]); Vue.set(state, `path_min_${axis}`, toolpath.bounds.min[axis]);
Vue.set(state, 'path_max_' + axis, toolpath.bounds.max[axis]); Vue.set(state, `path_max_${axis}`, toolpath.bounds.max[axis]);
} }
} }
} }
} }
}, },
submit_mdi: function() { submit_mdi: function() {
this.send(this.mdi); this.send(this.mdi);
if (!this.history.length || this.history[0] != this.mdi)
this.history.unshift(this.mdi);
this.mdi = '';
},
if (!this.history.length || this.history[0] != this.mdi) {
this.history.unshift(this.mdi);
}
this.mdi = "";
},
mdi_start_pause: function() { mdi_start_pause: function() {
if (this.state.xx == 'RUNNING') this.pause(); if (this.state.xx == "RUNNING") {
this.pause();
else if (this.state.xx == 'STOPPING' || this.state.xx == 'HOLDING') } else if (this.state.xx == "STOPPING" || this.state.xx == "HOLDING") {
this.unpause(); this.unpause();
} else {
else this.submit_mdi(); this.submit_mdi();
}
}, },
load_history: function(index) { load_history: function(index) {
this.mdi = this.history[index]; this.mdi = this.history[index];
}, },
open: function() {
open: function (e) { utils.clickFileInput("gcode-file-input");
// 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) {
upload: async function (e) {
const files = e.target.files || e.dataTransfer.files; const files = e.target.files || e.dataTransfer.files;
if (!files.length) { if (!files.length) {
return; return;
} }
const file = files[0]; const file = files[0];
const extension = file.name.split(".").pop(); const extension = file.name.split(".").pop();
switch (extension.toLowerCase()) { switch (extension.toLowerCase()) {
case "nc": case "nc":
@@ -588,126 +362,137 @@ module.exports = {
return; return;
} }
const fd = new FormData(); SvelteComponents.showDialog("Upload", {
file,
fd.append('gcode', file); onComplete: () => {
try {
await api.upload('file', fd);
this.last_file_time = undefined; // Force reload this.last_file_time = undefined; // Force reload
this.$broadcast('gcode-reload', file.name); this.$broadcast("gcode-reload", file.name);
} catch (err) {
api.alert('Upload failed', err)
} }
});
}, },
delete_current: function() { delete_current: function() {
if (this.state.selected) { if (this.state.selected) {
api.delete('file/' + this.state.selected); api.delete(`file/${this.state.selected}`);
} }
this.deleteGCode = false; this.deleteGCode = false;
}, },
delete_all: function() { delete_all: function() {
api.delete('file'); api.delete("file");
this.deleteGCode = false; this.deleteGCode = false;
}, },
home: function(axis) { home: function(axis) {
this.ask_home = false; this.ask_home = false;
this.ask_home_msg = false;
if (typeof axis == 'undefined') api.put('home'); if (typeof axis == "undefined") {
api.put("home");
else { } else if (this[axis].homingMode != "manual") {
if (this[axis].homingMode != 'manual') api.put('home/' + axis); api.put(`home/${axis}`);
else this.manual_home[axis] = true; } else {
SvelteComponents.showDialog("ManualHomeAxis", { axis });
} }
}, },
set_home: function(axis, position) { set_home: function(axis, position) {
this.manual_home[axis] = false; api.put(`home/${axis}/set`, { position: parseFloat(position) });
api.put('home/' + axis + '/set', {position: parseFloat(position)});
}, },
unhome: function(axis) { unhome: function(axis) {
this.position_msg[axis] = false; api.put(`home/${axis}/clear`);
api.put('home/' + axis + '/clear');
}, },
show_set_position: function(axis) { show_set_position: function(axis) {
this.axis_position = 0; SvelteComponents.showDialog("SetAxisPosition", { axis });
this.position_msg[axis] = true;
}, },
show_toolpath_msg : function(axis) { showMoveToZeroDialog: function(axes) {
this.toolpath_msg[axis] = true; SvelteComponents.showDialog("MoveToZero", { axes });
}, },
showToolpathMessageDialog: function(axis) {
SvelteComponents.showDialog("Message", { title: this[axis].toolmsg });
},
set_position: function(axis, position) { set_position: function(axis, position) {
this.position_msg[axis] = false; api.put(`position/${axis}`, { "position": parseFloat(position) });
api.put('position/' + axis, {'position': parseFloat(position)});
}, },
zero_all: function() { zero_all: function() {
for (var axis of 'xyzabc') for (const axis of "xyzabc") {
if (this[axis].enabled) this.zero(axis); if (this[axis].enabled) {
this.zero(axis);
}
}
}, },
zero: function(axis) { zero: function(axis) {
if (typeof axis == 'undefined') this.zero_all(); if (typeof axis == "undefined") {
else this.set_position(axis, 0); this.zero_all();
} else {
this.set_position(axis, 0);
}
}, },
start_pause: function() { start_pause: function() {
if (this.state.xx == 'RUNNING') this.pause(); if (this.state.xx == "RUNNING") {
this.pause();
else if (this.state.xx == 'STOPPING' || this.state.xx == 'HOLDING') } else if (this.state.xx == "STOPPING" || this.state.xx == "HOLDING") {
this.unpause(); this.unpause();
} else {
else this.start(); this.start();
}
}, },
start: function() {
api.put("start");
},
start: function () {api.put('start')}, pause: function() {
pause: function () {api.put('pause')}, 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')},
unpause: function() {
api.put("unpause");
},
override_feed: function () {api.put('override/feed/' + this.feed_override)}, 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() { override_speed: function() {
api.put('override/speed/' + this.speed_override) api.put(`override/speed/${this.speed_override}`);
}, },
current: function(axis, value) { current: function(axis, value) {
var x = value / 32.0; const x = value / 32.0;
if (this.state[axis + 'pl'] == x) return; if (this.state[`${axis}pl`] == x) {
return;
}
var data = {}; const data = {};
data[axis + 'pl'] = x; data[`${axis}pl`] = x;
this.send(JSON.stringify(data)); this.send(JSON.stringify(data));
},
showProbeDialog: function(probeType) {
SvelteComponents.showDialog("Probe", { probeType });
} }
}, },
mixins: [ require("./axis-vars") ]
mixins: [require('./axis-vars')] };
}

View File

@@ -1,69 +1,49 @@
/******************************************************************************\ "use strict";
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) { module.exports = function(prefix) {
if (typeof prefix == 'undefined') prefix = ''; if (typeof prefix == "undefined") {
prefix = "";
}
var cookie = { const cookie = {
get: function(name, defaultValue) { get: function(name, defaultValue) {
var decodedCookie = decodeURIComponent(document.cookie); const decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';'); const ca = decodedCookie.split(";");
name = prefix + name + '='; name = `${prefix + name}=`;
for (var i = 0; i < ca.length; i++) { for (let i = 0; i < ca.length; i++) {
var c = ca[i]; let c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1); while (c.charAt(0) == " ") {
if (!c.indexOf(name)) return c.substring(name.length, c.length); c = c.substring(1);
}
if (!c.indexOf(name)) {
return c.substring(name.length, c.length);
}
} }
return defaultValue; return defaultValue;
}, },
set: function(name, value, days) { set: function(name, value, days) {
var offset = 2147483647; // Max value let offset = 2147483647; // Max value
if (typeof days != 'undefined') offset = days * 24 * 60 * 60 * 1000; if (typeof days != "undefined") {
var d = new Date(); offset = days * 24 * 60 * 60 * 1000;
d.setTime(d.getTime() + offset); }
var expires = 'expires=' + d.toUTCString();
document.cookie = prefix + name + '=' + value + ';' + expires + ';path=/';
},
const d = new Date();
d.setTime(d.getTime() + offset);
const expires = `expires=${d.toUTCString()}`;
document.cookie = `${prefix}${name}=${value};${expires};path=/`;
},
set_bool: function(name, value) { set_bool: function(name, value) {
cookie.set(name, value ? 'true' : 'false'); cookie.set(name, value ? "true" : "false");
}, },
get_bool: function(name, defaultValue) { get_bool: function(name, defaultValue) {
return cookie.get(name, defaultValue ? 'true' : 'false') == 'true'; return cookie.get(name, defaultValue ? "true" : "false") == "true";
}
} }
};
return cookie; return cookie;
} };

View File

@@ -1,97 +1,74 @@
/******************************************************************************\ "use strict";
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 = {
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
'/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;'}
const entityMap = {
"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;",
"/": "&#x2F;", "`": "&#x60;", "=": "&#x3D;" };
function escapeHTML(s) { function escapeHTML(s) {
return s.replace(/[&<>"'`=\/]/g, function (c) {return entityMap[c]}) return s.replace(/[&<>"'`=\\/]/g, function(c) {
return entityMap[c];
});
} }
module.exports = { module.exports = {
template: '#gcode-viewer-template', template: "#gcode-viewer-template",
data: function() { data: function() {
return { return {
empty: true, empty: true,
file: '', file: "",
line: -1, line: -1
scrolling: false };
}
}, },
events: { events: {
'gcode-load': function (file) {this.load(file)}, "gcode-load": function(file) {
'gcode-clear': function () {this.clear()}, this.load(file);
'gcode-reload': function (file) {this.reload(file)}, },
'gcode-line': function (line) {this.update_line(line)} "gcode-clear": function() {
this.clear();
},
"gcode-reload": function(file) {
this.reload(file);
},
"gcode-line": function(line) {
this.update_line(line);
}
}, },
ready: function() { ready: function() {
this.clusterize = new Clusterize({ this.clusterize = new Clusterize({
rows: [], rows: [],
scrollElem: $(this.$el).find('.clusterize-scroll')[0], scrollElem: this.$el.querySelector(".clusterize-scroll"),
contentElem: $(this.$el).find('.clusterize-content')[0], contentElem: this.$el.querySelector(".clusterize-content"),
no_data_text: 'GCode view...', no_data_text: "GCode view...",
callbacks: { clusterChanged: this.highlight } callbacks: { clusterChanged: this.highlight }
}); });
}, },
attached: function() { attached: function() {
if (typeof this.clusterize != 'undefined') if (typeof this.clusterize != "undefined") {
this.clusterize.refresh(true); this.clusterize.refresh(true);
}
}, },
methods: { methods: {
load: async function(file) { load: async function(file) {
if (file == this.file) return; if (file == this.file) {
return;
}
this.clear(); this.clear();
this.file = file; this.file = file;
if (!file) {
return;
}
if (!file) return; const response = await fetch(`/api/file/${file}`, { cache: "no-cache" });
const response = await fetch(`/api/file/${file}?${Math.random()}`);
const text = await response.text(); const text = await response.text();
if (text.length > 20e6) { if (text.length > 20e6) {
this.clusterize.update(['File is large - gcode view disabled']); this.clusterize.update([ "File is large - gcode view disabled" ]);
} else { } else {
const lines = escapeHTML(text.trimRight()) const lines = escapeHTML(text.trimRight())
.split(/[\r\n]/) .split(/[\r\n]/)
@@ -105,64 +82,71 @@ module.exports = {
Vue.nextTick(this.update_line); Vue.nextTick(this.update_line);
}, },
clear: function() { clear: function() {
this.empty = true; this.empty = true;
this.file = ''; this.file = "";
this.line = -1; this.line = -1;
this.clusterize.clear(); this.clusterize.clear();
}, },
reload: function(file) { reload: function(file) {
if (file != this.file) return; if (file == this.file) {
return;
}
this.clear(); this.clear();
this.load(file); this.load(file);
}, },
highlight: function() { highlight: function() {
var e = $(this.$el).find('.highlight'); const highlights = this.$el.querySelectorAll(".highlight");
if (e.length) e.removeClass('highlight'); for (const highlight of highlights) {
highlight.className = (highlight.className || "")
.split(" ")
.filter(c => c !== "highlight")
.join(" ");
}
e = $(this.$el).find('.ln' + this.line); const lines = this.$el.querySelectorAll(`.ln${this.line}`);
if (e.length) e.addClass('highlight'); for (const line of lines) {
line.className = (line.className || "")
.split(" ")
.filter(c => c !== "highlight")
.concat([ "highlight" ])
.join(" ");
}
}, },
update_line: function(line) { update_line: function(line) {
if (typeof line != 'undefined') { if (typeof line != "undefined") {
if (this.line == line) return; if (this.line == line) {
return;
}
this.line = line; this.line = line;
} else {
line = this.line;
}
} else line = this.line; const totalLines = this.clusterize.getRowsAmount();
var totalLines = this.clusterize.getRowsAmount(); if (line <= 0) {
line = 1;
}
if (line <= 0) line = 1; if (totalLines < line) {
if (totalLines < line) line = totalLines; line = totalLines;
}
var e = $(this.$el).find('.clusterize-scroll'); const scroll = this.$el.querySelector(".clusterize-scroll");
var lineHeight = e[0].scrollHeight / totalLines; const lineHeight = scroll.scrollHeight / totalLines;
var linesPerPage = Math.floor(e[0].clientHeight / lineHeight); const linesPerPage = Math.floor(scroll.clientHeight / lineHeight);
var current = e[0].scrollTop / lineHeight; const target = line - 1 - Math.floor(linesPerPage / 2);
var target = line - 1 - Math.floor(linesPerPage / 2);
// Update scroll position // Update scroll position
if (!this.scrolling) { scroll.scrollTop = target * lineHeight;
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); Vue.nextTick(this.highlight);
} }
} }
} };

14
src/js/help-view.js Normal file
View File

@@ -0,0 +1,14 @@
module.exports = {
template: "#help-view-template",
attached: function() {
this.svelteComponent = SvelteComponents.createComponent(
"HelpView",
document.getElementById("help")
);
},
detached: function() {
this.svelteComponent.$destroy();
}
};

View File

@@ -1,65 +1,44 @@
/******************************************************************************\ "use strict";
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');
const modbus = require("./modbus.js");
module.exports = { module.exports = {
template: '#indicators-template', template: "#indicators-template",
props: ['state'], props: [ "state" ],
computed: { computed: {
modbus_status: function () {return modbus.status_to_string(this.state.mx)}, modbus_status: function() {
return modbus.status_to_string(this.state.mx);
},
sense_error: function() { sense_error: function() {
var error = ''; let error = "";
if (this.state.motor_voltage_sense_error) error += 'Motor voltage\n'; if (this.state.motor_voltage_sense_error) {
if (this.state.motor_current_sense_error) error += 'Motor current\n'; error += "Motor voltage\n";
if (this.state.load1_sense_error) error += 'Load 1\n'; }
if (this.state.load2_sense_error) error += 'Load 2\n'; if (this.state.motor_current_sense_error) {
if (this.state.vdd_current_sense_error) error += 'Vdd current\n'; 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; return error;
} }
}, },
methods: { methods: {
is_motor_enabled: function(motor) { is_motor_enabled: function(motor) {
return typeof this.state[motor + 'me'] != 'undefined' && return typeof this.state[`${motor}me`] != "undefined" && this.state[`${motor}me`];
this.state[motor + 'me'];
}, },
get_min_pin: function(motor) { get_min_pin: function(motor) {
switch (motor) { switch (motor) {
case 0: return 3; case 0: return 3;
@@ -69,7 +48,6 @@ module.exports = {
} }
}, },
get_max_pin: function(motor) { get_max_pin: function(motor) {
switch (motor) { switch (motor) {
case 0: return 4; case 0: return 4;
@@ -79,29 +57,38 @@ module.exports = {
} }
}, },
motor_fault_class: function(motor, bit) { motor_fault_class: function(motor, bit) {
if (typeof motor == 'undefined') { if (typeof motor == "undefined") {
var status = this.state['fa']; const status = this.state["fa"];
if (typeof status == 'undefined') return 'fa-question';
return 'fa-thumbs-' + (status ? 'down error' : 'up success') if (typeof status == "undefined") {
return "fa-question";
} }
var flags = this.state[motor + 'df']; return `fa-thumbs-${status ? "down error" : "up success"}`;
if (typeof flags == 'undefined') return 'fa-question'; }
return (flags & (1 << bit)) ? 'fa-thumbs-down error' :
'fa-thumbs-up success'; const 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) { motor_reset: function(motor) {
if (typeof motor == 'undefined') { if (typeof motor == "undefined") {
var cmd = ''; let cmd = "";
for (var i = 0; i < 4; i++) for (let i = 0; i < 4; i++) {
cmd += '\\$' + i + 'df=0\n'; cmd += `\\$${i}df=0\n`;
this.$dispatch('send', cmd); }
} else this.$dispatch('send', '\\$' + motor + 'df=0'); this.$dispatch("send", cmd);
} else {
this.$dispatch("send", `\\$${motor}df=0`);
} }
} }
} }
};

View File

@@ -1,177 +1,155 @@
/******************************************************************************\ "use strict";
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 = { module.exports = {
template: "#io-indicator-template", template: "#io-indicator-template",
props: ['name', 'state'], props: [ "name", "state" ],
computed: { computed: {
klass: function() { klass: function() {
if (this.name == 'min-switch-0') return this.get_motor_min_class(0); switch (this.name) {
if (this.name == 'min-switch-1') return this.get_motor_min_class(1); case "min-switch-0": return this.get_motor_min_class(0);
if (this.name == 'min-switch-2') return this.get_motor_min_class(2); case "min-switch-1": return this.get_motor_min_class(1);
if (this.name == 'min-switch-3') return this.get_motor_min_class(3); case "min-switch-2": return this.get_motor_min_class(2);
if (this.name == 'max-switch-0') return this.get_motor_max_class(0); case "min-switch-3": return this.get_motor_min_class(3);
if (this.name == 'max-switch-1') return this.get_motor_max_class(1); case "max-switch-0": return this.get_motor_max_class(0);
if (this.name == 'max-switch-2') return this.get_motor_max_class(2); case "max-switch-1": return this.get_motor_max_class(1);
if (this.name == 'max-switch-3') return this.get_motor_max_class(3); case "max-switch-2": return this.get_motor_max_class(2);
if (this.name == 'estop') return this.get_input_class('ew', 'et'); case "max-switch-3": return this.get_motor_max_class(3);
if (this.name == 'probe') return this.get_input_class('pw', 'pt'); case "estop": return this.get_input_class("ew", "et");
if (this.name == 'load-1') return this.get_output_class('1'); case "probe": return this.get_input_class("pw", "pt");
if (this.name == 'load-2') return this.get_output_class('2'); case "load-1": return this.get_output_class("1");
if (this.name == 'fault') return this.get_output_class('f'); case "load-2": return this.get_output_class("2");
if (this.name == 'tool-enable-mode') return this.get_output_class('e'); case "fault": return this.get_output_class("f");
if (this.name == 'tool-direction-mode') return this.get_output_class('d'); case "tool-enable-mode": return this.get_output_class("e");
}, case "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');
} }
}, },
tooltip: function() {
switch (this.name) {
case "min-switch-0": return this.get_motor_min_tooltip(0);
case "min-switch-1": return this.get_motor_min_tooltip(1);
case "min-switch-2": return this.get_motor_min_tooltip(2);
case "min-switch-3": return this.get_motor_min_tooltip(3);
case "max-switch-0": return this.get_motor_max_tooltip(0);
case "max-switch-1": return this.get_motor_max_tooltip(1);
case "max-switch-2": return this.get_motor_max_tooltip(2);
case "max-switch-3": return this.get_motor_max_tooltip(3);
case "estop": return this.get_input_tooltip("ew", "et");
case "probe": return this.get_input_tooltip("pw", "pt");
case "load-1": return this.get_output_tooltip("1");
case "load-2": return this.get_output_tooltip("2");
case "fault": return this.get_output_tooltip("f");
case "tool-direction-mode": return this.get_output_tooltip("d");
case "tool-enable-mode": return this.get_output_tooltip("e");
}
}
},
methods: { methods: {
get_io_state_class: function(active, state) { get_io_state_class: function(active, state) {
if (typeof active == 'undefined' || typeof state == 'undefined') if (typeof active == "undefined" || typeof state == "undefined") {
return 'fa-exclamation-triangle warn'; return "fa-exclamation-triangle warn";
}
if (state == 2) return 'fa-circle-o'; if (state == 2) {
return "fa-circle-o";
}
return (state ? 'fa-plus-circle' : 'fa-minus-circle') + ' ' + const icon = state ? "fa-plus-circle" : "fa-minus-circle";
(active ? 'active' : 'inactive'); return `${icon} ${active ? "active" : "inactive"}`;
}, },
get_input_active: function(stateCode, typeCode) { get_input_active: function(stateCode, typeCode) {
var type = this.state[typeCode]; const type = this.state[typeCode];
var state = this.state[stateCode]; const state = this.state[stateCode];
if (type == 1) return !state; // Normally open if (type == 1) {
else if (type == 2) return state; // Normally closed return !state; // Normally open
} else if (type == 2) {
return state; // Normally closed
}
return false return false;
}, },
get_input_class: function(stateCode, typeCode) { get_input_class: function(stateCode, typeCode) {
return this.get_io_state_class(this.get_input_active(stateCode, typeCode), return this.get_io_state_class(this.get_input_active(stateCode, typeCode), this.state[stateCode]);
this.state[stateCode]);
}, },
get_output_class: function(output) { get_output_class: function(output) {
return this.get_io_state_class(this.state[output + 'oa'], return this.get_io_state_class(this.state[`${output}oa`], this.state[`${output}os`]);
this.state[output + 'os']);
}, },
get_motor_min_class: function(motor) { get_motor_min_class: function(motor) {
return this.get_input_class(motor + 'lw', motor + 'ls'); return this.get_input_class(`${motor}lw`, `${motor}ls`);
}, },
get_motor_max_class: function(motor) { get_motor_max_class: function(motor) {
return this.get_input_class(motor + 'xw', motor + 'xs'); return this.get_input_class(`${motor}xw`, `${motor}xs`);
}, },
get_tooltip: function(mode, active, state) { get_tooltip: function(mode, active, state) {
if (typeof mode == 'undefined' || typeof active == 'undefined' || if (typeof mode == "undefined" || typeof active == "undefined" || typeof state == "undefined") {
typeof state == 'undefined') return 'Invalid'; return "Invalid";
}
if (state == 0) state = 'Lo/Gnd'; if (state == 0) {
else if (state == 1) state = 'Hi/+3.3v'; state = "Lo/Gnd";
else if (state == 2) state = 'Tristated'; } else if (state == 1) {
else return 'Invalid'; state = "Hi/+3.3v";
} else if (state == 2) {
state = "Tristated";
} else {
return "Invalid";
}
return 'Mode: ' + mode + '\nActive: ' + (active ? 'True' : 'False') + return `Mode: ${mode}\nActive: ${active ? "True" : "False"}\nLevel: ${state}`;
'\nLevel: ' + state;
}, },
get_input_tooltip: function(stateCode, typeCode) { get_input_tooltip: function(stateCode, typeCode) {
var type = this.state[typeCode]; let type = this.state[typeCode];
if (type == 0) return 'Disabled'; if (type == 0) {
else if (type == 1) type = 'Normally open'; return "Disabled";
else if (type == 2) type = 'Normally closed'; } else if (type == 1) {
type = "Normally open";
} else if (type == 2) {
type = "Normally closed";
}
var active = this.get_input_active(stateCode, typeCode); const active = this.get_input_active(stateCode, typeCode);
var state = this.state[stateCode]; const state = this.state[stateCode];
return this.get_tooltip(type, active, state); return this.get_tooltip(type, active, state);
}, },
get_output_tooltip: function(output) { get_output_tooltip: function(output) {
var mode = this.state[output + 'om']; let 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']; switch (mode) {
var state = this.state[output + 'os']; case 0: return "Disabled";
case 1: mode = "Lo/Hi"; break;
case 2: mode = "Hi/Lo"; break;
case 3: mode = "Tri/Lo"; break;
case 4: mode = "Tri/Hi"; break;
case 5: mode = "Lo/Tri"; break;
case 6: mode = "Hi/Tri"; break;
default:
mode = undefined;
}
const active = this.state[`${output}oa`];
const state = this.state[`${output}os`];
return this.get_tooltip(mode, active, state); return this.get_tooltip(mode, active, state);
}, },
get_motor_min_tooltip: function(motor) { get_motor_min_tooltip: function(motor) {
return this.get_input_tooltip(motor + 'lw', motor + 'ls'); return this.get_input_tooltip(`${motor}lw`, `${motor}ls`);
}, },
get_motor_max_tooltip: function(motor) { get_motor_max_tooltip: function(motor) {
return this.get_input_tooltip(motor + 'xw', motor + 'xs'); return this.get_input_tooltip(`${motor}xw`, `${motor}xs`);
}
} }
} }
};

View File

@@ -1,42 +1,13 @@
/******************************************************************************\ "use strict";
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 = { module.exports = {
template: '#io-view-template', template: "#io-view-template",
props: ['config', 'template', 'state'], props: [ "config", "template", "state" ],
events: { events: {
'input-changed': function() { "input-changed": function() {
this.$dispatch('config-changed'); this.$dispatch("config-changed");
return false; return false;
} }
} }
} };

View File

@@ -1,120 +1,118 @@
/******************************************************************************\ "use strict";
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) { function cookie_get(name) {
var decodedCookie = decodeURIComponent(document.cookie); const decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';'); const ca = decodedCookie.split(";");
name = name + '='; name = `${name}=`;
for (var i = 0; i < ca.length; i++) { for (let i = 0; i < ca.length; i++) {
var c = ca[i]; let c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1); while (c.charAt(0) == " ") {
if (!c.indexOf(name)) return c.substring(name.length, c.length); c = c.substring(1);
}
} }
if (!c.indexOf(name)) {
return c.substring(name.length, c.length);
}
}
}
function cookie_set(name, value, days) { function cookie_set(name, value, days) {
var d = new Date(); const d = new Date();
d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000); d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
var expires = 'expires=' + d.toUTCString(); const expires = `expires=${d.toUTCString()}`;
document.cookie = name + '=' + value + ';' + expires + ';path=/'; document.cookie = `${name}=${value};${expires};path=/`;
} }
const uuid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+";
var uuid_chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+';
function uuid(length) { function uuid(length) {
if (typeof length == 'undefined') length = 52; 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
} }
let s = "";
for (let i = 0; i < length; i++) {
s += uuid_chars[Math.floor(Math.random() * uuid_chars.length)];
}
$(function() { return s;
if (typeof cookie_get('client-id') == 'undefined') }
cookie_set('client-id', uuid(), 10000);
// Vue debugging window.onload = function() {
Vue.config.debug = true; if (typeof cookie_get("client-id") == "undefined") {
//Vue.util.warn = function (msg) {console.debug('[Vue warn]: ' + msg)} cookie_set("client-id", uuid(), 10000);
}
// Register global components // Register global components
Vue.component('templated-input', require('./templated-input')); Vue.component("templated-input", require("./templated-input"));
Vue.component('message', require('./message')); Vue.component("message", require("./message"));
Vue.component('indicators', require('./indicators')); Vue.component("indicators", require("./indicators"));
Vue.component('io-indicator', require('./io-indicator')); Vue.component("io-indicator", require("./io-indicator"));
Vue.component('console', require('./console')); Vue.component("console", require("./console"));
Vue.component('unit-value', require('./unit-value')); Vue.component("unit-value", require("./unit-value"));
Vue.filter("number", function(value) {
if (isNaN(value)) {
return "NaN";
}
Vue.filter('number', function (value) {
if (isNaN(value)) return 'NaN';
return value.toLocaleString(); return value.toLocaleString();
}); });
Vue.filter('percent', function (value, precision) { Vue.filter("percent", function(value, precision) {
if (typeof value == 'undefined') return ''; if (typeof value == "undefined") {
if (typeof precision == 'undefined') precision = 2; return "";
return (value * 100.0).toFixed(precision) + '%'; }
if (typeof precision == "undefined") {
precision = 2;
}
return `${(value * 100.0).toFixed(precision)}%`;
}); });
Vue.filter('non_zero_percent', function (value, precision) { Vue.filter("non_zero_percent", function(value, precision) {
if (!value) return ''; if (!value) {
if (typeof precision == 'undefined') precision = 2; return "";
return (value * 100.0).toFixed(precision) + '%'; }
if (typeof precision == "undefined") {
precision = 2;
}
return `${(value * 100.0).toFixed(precision)}%`;
}); });
Vue.filter('fixed', function (value, precision) { Vue.filter("fixed", function(value, precision) {
if (typeof value == 'undefined') return '0'; if (typeof value == "undefined") {
return parseFloat(value).toFixed(precision) return "0";
}
return parseFloat(value).toFixed(precision);
}); });
Vue.filter('upper', function (value) { Vue.filter("upper", function(value) {
if (typeof value == 'undefined') return ''; if (typeof value == "undefined") {
return value.toUpperCase() return "";
}
return value.toUpperCase();
}); });
Vue.filter('time', function (value, precision) { Vue.filter("time", function(value, precision) {
if (isNaN(value)) return ''; if (isNaN(value)) {
if (isNaN(precision)) precision = 0; return "";
}
var MIN = 60; if (isNaN(precision)) {
var HR = MIN * 60; precision = 0;
var DAY = HR * 24; }
var parts = [];
const MIN = 60;
const HR = MIN * 60;
const DAY = HR * 24;
const parts = [];
if (DAY <= value) { if (DAY <= value) {
parts.push(Math.floor(value / DAY)); parts.push(Math.floor(value / DAY));
@@ -129,19 +127,22 @@ $(function() {
if (MIN <= value) { if (MIN <= value) {
parts.push(Math.floor(value / MIN)); parts.push(Math.floor(value / MIN));
value %= MIN; value %= MIN;
} else {
} else parts.push(0); parts.push(0);
}
parts.push(value); parts.push(value);
for (var i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
parts[i] = parts[i].toFixed(i == parts.length - 1 ? precision : 0); parts[i] = parts[i].toFixed(i == parts.length - 1 ? precision : 0);
if (i && parts[i] < 10) parts[i] = '0' + parts[i]; if (i && parts[i] < 10) {
parts[i] = `0${parts[i]}`;
}
} }
return parts.join(':'); return parts.join(":");
}); });
// Vue app // Vue app
require('./app'); require("./app");
}); };

View File

@@ -1,35 +1,7 @@
/******************************************************************************\ "use strict";
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 = { module.exports = {
template: '#message-template', template: "#message-template",
props: { props: {
show: { show: {
@@ -44,4 +16,4 @@ module.exports = {
twoWay: false twoWay: false
} }
} }
} };

View File

@@ -1,48 +1,20 @@
/******************************************************************************\ "use strict";
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 = { module.exports = {
replace: true, replace: true,
template: '#modbus-reg-view-template', template: "#modbus-reg-view-template",
props: ['index', 'model', 'template', 'enable'], props: [ "index", "model", "template", "enable" ],
computed: { computed: {
has_user_value: function() { has_user_value: function() {
var type = this.model['reg-type']; const type = this.model["reg-type"];
return type.indexOf('write') != -1 || type.indexOf('fixed') != -1; return type.includes("write") || type.includes("fixed") || type.includes("scaled");
} }
}, },
methods: { methods: {
change: function () {this.$dispatch('input-changed')} change: function() {
this.$dispatch("input-changed");
} }
} }
};

View File

@@ -1,35 +1,7 @@
/******************************************************************************\ "use strict";
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 // Must match modbus.c
var exports = { const constants = {
DISCONNECTED: 0, DISCONNECTED: 0,
OK: 1, OK: 1,
CRC: 2, CRC: 2,
@@ -37,15 +9,15 @@ var exports = {
TIMEDOUT: 4 TIMEDOUT: 4
}; };
module.exports = {
exports.status_to_string = ...constants,
function (status) { status_to_string: function(status) {
if (status == exports.OK) return 'Ok'; switch (status) {
if (status == exports.CRC) return 'CRC error'; case constants.OK: return "Ok";
if (status == exports.INVALID) return 'Invalid response'; case constants.CRC: return "CRC error";
if (status == exports.TIMEDOUT) return 'Timedout'; case constants.INVALID: return "Invalid response";
return 'Disconnected'; case constants.TIMEDOUT: return "Timedout";
default: return "Disconnected";
} }
}
};
module.exports = exports;

View File

@@ -1,127 +1,108 @@
/******************************************************************************\ "use strict";
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 = { module.exports = {
template: '#motor-view-template', template: "#motor-view-template",
props: ['index', 'config', 'template', 'state'], props: [ "index", "config", "template", "state" ],
computed: { computed: {
metric: function () {return this.$root.metric()}, metric: function() {
return this.$root.display_units === "METRIC";
},
is_slave: function() { is_slave: function() {
for (var i = 0; i < this.index; i++) for (let i = 0; i < this.index; i++) {
if (this.motor.axis == this.config.motors[i].axis) if (this.motor.axis == this.config.motors[i].axis) {
return true; return true;
}
}
return false; return false;
}, },
motor: function() {
motor: function () {return this.config.motors[this.index]}, return this.config.motors[this.index];
},
invalidMaxVelocity: function() { invalidMaxVelocity: function() {
return this.maxMaxVelocity < this.motor['max-velocity']; return this.maxMaxVelocity < this.motor["max-velocity"];
}, },
maxMaxVelocity: function() { maxMaxVelocity: function() {
return 1 * (15 * this.umPerStep / this.motor['microsteps']).toFixed(3); return 1 * (15 * this.umPerStep / this.motor["microsteps"]).toFixed(3);
}, },
ustepPerSec: function() { ustepPerSec: function() {
return this.rpm * this.stepsPerRev * this.motor['microsteps'] / 60; return this.rpm * this.stepsPerRev * this.motor["microsteps"] / 60;
}, },
rpm: function() { rpm: function() {
return 1000 * this.motor['max-velocity'] / this.motor['travel-per-rev']; return 1000 * this.motor["max-velocity"] / this.motor["travel-per-rev"];
}, },
gForce: function() {
return this.motor["max-accel"] * 0.0283254504;
},
gForce: function () {return this.motor['max-accel'] * 0.0283254504}, gForcePerMin: function() {
gForcePerMin: function () {return this.motor['max-jerk'] * 0.0283254504}, return this.motor["max-jerk"] * 0.0283254504;
stepsPerRev: function () {return 360 / this.motor['step-angle']}, },
stepsPerRev: function() {
return 360 / this.motor["step-angle"];
},
umPerStep: function() { umPerStep: function() {
return this.motor['travel-per-rev'] * this.motor['step-angle'] / 0.36 return this.motor["travel-per-rev"] * this.motor["step-angle"] / 0.36;
}, },
milPerStep: function() {
milPerStep: function () {return this.umPerStep / 25.4}, return this.umPerStep / 25.4;
},
invalidStallVelocity: function() { invalidStallVelocity: function() {
if(!this.motor['homing-mode'].startsWith('stall-')) return false; if (!this.motor["homing-mode"].startsWith("stall-")) {
return this.maxStallVelocity < this.motor['search-velocity']; return false;
}
return this.maxStallVelocity < this.motor["search-velocity"];
}, },
stallRPM: function() { stallRPM: function() {
var v = this.motor['search-velocity']; const v = this.motor["search-velocity"];
return 1000 * v / this.motor['travel-per-rev']; return 1000 * v / this.motor["travel-per-rev"];
}, },
maxStallVelocity: function() { maxStallVelocity: function() {
var maxRate = 900000/this.motor['stall-sample-time']; const maxRate = 900000 / this.motor["stall-sample-time"];
var ustep = this.motor['stall-microstep']; const ustep = this.motor["stall-microstep"];
var angle = this.motor['step-angle']; const angle = this.motor["step-angle"];
var travel = this.motor['travel-per-rev']; const travel = this.motor["travel-per-rev"];
var maxStall = maxRate * 60/ 360 /1000 * angle/ ustep * travel; const maxStall = maxRate * 60 / 360 / 1000 * angle / ustep * travel;
return 1 * maxStall.toFixed(3); return 1 * maxStall.toFixed(3);
}, },
stallUStepPerSec: function() { stallUStepPerSec: function() {
var ustep = this.motor['stall-microstep']; const ustep = this.motor["stall-microstep"];
return this.stallRPM * this.stepsPerRev * ustep / 60; return this.stallRPM * this.stepsPerRev * ustep / 60;
} }
}, },
events: { events: {
'input-changed': function() { "input-changed": function() {
Vue.nextTick(function() { Vue.nextTick(function() {
// Limit max-velocity // Limit max-velocity
if (this.invalidMaxVelocity) if (this.invalidMaxVelocity) {
this.$set('motor["max-velocity"]', this.maxMaxVelocity); this.$set('motor["max-velocity"]', this.maxMaxVelocity);
}
//Limit stall-velocity //Limit stall-velocity
if(this.invalidStallVelocity) if (this.invalidStallVelocity) {
this.$set('motor["search-velocity"]', this.maxStallVelocity); this.$set('motor["search-velocity"]', this.maxStallVelocity);
}
this.$dispatch('config-changed'); this.$dispatch("config-changed");
}.bind(this)) }.bind(this));
return false; return false;
} }
@@ -129,8 +110,11 @@ module.exports = {
methods: { methods: {
show: function(name, templ) { show: function(name, templ) {
if(templ.hmodes == undefined) return true; if (templ.hmodes == undefined) {
return templ.hmodes.indexOf(this.motor['homing-mode']) != -1; return true;
} }
return templ.hmodes.indexOf(this.motor["homing-mode"]) != -1;
} }
} }
};

View File

@@ -7,7 +7,7 @@
* @author jcoffland / https://buildbotics.com/ * @author jcoffland / https://buildbotics.com/
*/ */
'use strict' "use strict";
// This set of controls performs orbiting, dollying (zooming), and panning. // This set of controls performs orbiting, dollying (zooming), and panning.
// Unlike TrackballControls, it maintains the "up" direction object.up // Unlike TrackballControls, it maintains the "up" direction object.up
@@ -17,8 +17,42 @@
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
// Pan - right mouse, or arrow keys / touch: two-finger move // Pan - right mouse, or arrow keys / touch: two-finger move
const OrbitControls = function(object, domElement) {
// internals
// eslint-disable-next-line @typescript-eslint/no-this-alias
const scope = this;
const changeEvent = { type: "change" };
const startEvent = { type: "start" };
const endEvent = { type: "end" };
const STATE = {
NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4
};
let state = STATE.NONE;
const EPS = 0.000001;
// current position in spherical coordinates
const spherical = new THREE.Spherical();
const sphericalDelta = new THREE.Spherical();
let scale = 1;
const panOffset = new THREE.Vector3();
let zoomChanged = false;
const rotateStart = new THREE.Vector2();
const rotateEnd = new THREE.Vector2();
const rotateDelta = new THREE.Vector2();
const panStart = new THREE.Vector2();
const panEnd = new THREE.Vector2();
const panDelta = new THREE.Vector2();
const dollyStart = new THREE.Vector2();
const dollyEnd = new THREE.Vector2();
const dollyDelta = new THREE.Vector2();
var OrbitControls = function (object, domElement) {
this.object = object; this.object = object;
this.domElement = domElement != undefined ? domElement : document; this.domElement = domElement != undefined ? domElement : document;
@@ -85,16 +119,19 @@ var OrbitControls = function (object, domElement) {
this.zoom0 = this.object.zoom; this.zoom0 = this.object.zoom;
// public methods // public methods
this.getPolarAngle = function () {return spherical.phi} this.getPolarAngle = function() {
this.getAzimuthalAngle = function () {return spherical.theta} return spherical.phi;
};
this.getAzimuthalAngle = function() {
return spherical.theta;
};
this.saveState = function() { this.saveState = function() {
scope.target0.copy(scope.target); scope.target0.copy(scope.target);
scope.position0.copy(scope.object.position); scope.position0.copy(scope.object.position);
scope.zoom0 = scope.object.zoom; scope.zoom0 = scope.object.zoom;
} };
this.reset = function() { this.reset = function() {
scope.target.copy(scope.target0); scope.target.copy(scope.target0);
@@ -106,22 +143,20 @@ var OrbitControls = function (object, domElement) {
scope.update(); scope.update();
state = STATE.NONE; state = STATE.NONE;
} };
this.update = function() { this.update = function() {
var offset = new THREE.Vector3(); const offset = new THREE.Vector3();
// so camera.up is the orbit axis // so camera.up is the orbit axis
var quat = new THREE.Quaternion() const quat = new THREE.Quaternion().setFromUnitVectors(object.up, new THREE.Vector3(0, 1, 0));
.setFromUnitVectors(object.up, new THREE.Vector3(0, 1, 0)); const quatInverse = quat.clone().inverse();
var quatInverse = quat.clone().inverse();
var lastPosition = new THREE.Vector3(); const lastPosition = new THREE.Vector3();
var lastQuaternion = new THREE.Quaternion(); const lastQuaternion = new THREE.Quaternion();
return function update() { return function update() {
var position = scope.object.position; const position = scope.object.position;
offset.copy(position).sub(scope.target); offset.copy(position).sub(scope.target);
@@ -131,28 +166,24 @@ var OrbitControls = function (object, domElement) {
// angle from z-axis around y-axis // angle from z-axis around y-axis
spherical.setFromVector3(offset); spherical.setFromVector3(offset);
if (scope.autoRotate && state == STATE.NONE) if (scope.autoRotate && state == STATE.NONE) {
rotateLeft(getAutoRotationAngle()); rotateLeft(getAutoRotationAngle());
}
spherical.theta += sphericalDelta.theta; spherical.theta += sphericalDelta.theta;
spherical.phi += sphericalDelta.phi; spherical.phi += sphericalDelta.phi;
// restrict theta to be between desired limits // restrict theta to be between desired limits
spherical.theta = spherical.theta = Math.max(scope.minAzimuthAngle, Math.min(scope.maxAzimuthAngle, spherical.theta));
Math.max(scope.minAzimuthAngle,
Math.min(scope.maxAzimuthAngle, spherical.theta));
// restrict phi to be between desired limits // restrict phi to be between desired limits
spherical.phi = spherical.phi = Math.max(scope.minPolarAngle, Math.min(scope.maxPolarAngle, spherical.phi));
Math.max(scope.minPolarAngle,
Math.min(scope.maxPolarAngle, spherical.phi));
spherical.makeSafe(); spherical.makeSafe();
spherical.radius *= scale; spherical.radius *= scale;
// restrict radius to be between desired limits // restrict radius to be between desired limits
spherical.radius = spherical.radius = Math.max(10, Math.min(scope.object.far * 0.8, spherical.radius));
Math.max(10, Math.min(scope.object.far * 0.8, spherical.radius));
// move target to panned location // move target to panned location
scope.target.add(panOffset); scope.target.add(panOffset);
@@ -178,9 +209,10 @@ var OrbitControls = function (object, domElement) {
// update condition is: // update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS // min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8 // using small-angle approximation cos(x/2) = 1 - x^2 / 8
if (zoomChanged || scale != 1 || if (zoomChanged
lastPosition.distanceToSquared(scope.object.position) > EPS || || scale != 1
8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS) { || lastPosition.distanceToSquared(scope.object.position) > EPS
|| 8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS) {
scope.dispatchEvent(changeEvent); scope.dispatchEvent(changeEvent);
@@ -193,187 +225,150 @@ var OrbitControls = function (object, domElement) {
} }
return false; return false;
} };
}() }();
this.dispose = function() { this.dispose = function() {
scope.domElement.removeEventListener('contextmenu', onContextMenu, false); scope.domElement.removeEventListener("contextmenu", onContextMenu, false);
scope.domElement.removeEventListener('mousedown', onMouseDown, false); scope.domElement.removeEventListener("mousedown", onMouseDown, false);
scope.domElement.removeEventListener('wheel', onMouseWheel, false); scope.domElement.removeEventListener("wheel", onMouseWheel, false);
scope.domElement.removeEventListener('touchstart', onTouchStart, false); scope.domElement.removeEventListener("touchstart", onTouchStart, false);
scope.domElement.removeEventListener('touchend', onTouchEnd, false); scope.domElement.removeEventListener("touchend", onTouchEnd, false);
scope.domElement.removeEventListener('touchmove', onTouchMove, false); scope.domElement.removeEventListener("touchmove", onTouchMove, false);
document.removeEventListener('mousemove', onMouseMove, false); document.removeEventListener("mousemove", onMouseMove, false);
document.removeEventListener('mouseup', onMouseUp, false); document.removeEventListener("mouseup", onMouseUp, false);
window.removeEventListener('keydown', onKeyDown, 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() { function getAutoRotationAngle() {
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
} }
function getZoomScale() {
return Math.pow(0.95, scope.zoomSpeed);
}
function getZoomScale() {return Math.pow(0.95, scope.zoomSpeed)} function rotateLeft(angle) {
function rotateLeft(angle) {sphericalDelta.theta -= angle} sphericalDelta.theta -= angle;
function rotateUp(angle) {sphericalDelta.phi -= angle} }
function rotateUp(angle) {
sphericalDelta.phi -= angle;
}
var panLeft = function () { const panLeft = function() {
var v = new THREE.Vector3(); const v = new THREE.Vector3();
return function panLeft(distance, objectMatrix) { return function panLeft(distance, objectMatrix) {
v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
v.multiplyScalar(-distance); v.multiplyScalar(-distance);
panOffset.add(v); panOffset.add(v);
} };
}() }();
const panUp = function() {
var panUp = function () { const v = new THREE.Vector3();
var v = new THREE.Vector3();
return function panUp(distance, objectMatrix) { return function panUp(distance, objectMatrix) {
if (scope.screenSpacePanning) v.setFromMatrixColumn(objectMatrix, 1); if (scope.screenSpacePanning) {
else { v.setFromMatrixColumn(objectMatrix, 1);
} else {
v.setFromMatrixColumn(objectMatrix, 0); v.setFromMatrixColumn(objectMatrix, 0);
v.crossVectors(scope.object.up, v); v.crossVectors(scope.object.up, v);
} }
v.multiplyScalar(distance); v.multiplyScalar(distance);
panOffset.add(v); panOffset.add(v);
} };
}() }();
function unknownCamera() { function unknownCamera() {
console.warn('WARNING: OrbitControls.js encountered an unknown camera ' + console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan & zoom disabled.");
'type - pan & zoom disabled.');
scope.enablePan = false; scope.enablePan = false;
scope.enableZoom = false; scope.enableZoom = false;
} }
// deltaX and deltaY are in pixels; right and down are positive // deltaX and deltaY are in pixels; right and down are positive
var pan = function () { const pan = function() {
var offset = new THREE.Vector3(); const offset = new THREE.Vector3();
return function pan(deltaX, deltaY) { return function pan(deltaX, deltaY) {
var element = scope.domElement === document ? const element = scope.domElement === document
scope.domElement.body : scope.domElement; ? scope.domElement.body
: scope.domElement;
if (scope.object.isPerspectiveCamera) { if (scope.object.isPerspectiveCamera) {
// perspective // perspective
offset.copy(scope.object.position).sub(scope.target); offset.copy(scope.object.position).sub(scope.target);
var targetDistance = offset.length(); let targetDistance = offset.length();
// half of the fov is center to top of screen // half of the fov is center to top of screen
targetDistance *= Math.tan((scope.object.fov / 2) * Math.PI / 180.0); targetDistance *= Math.tan((scope.object.fov / 2) * Math.PI / 180.0);
// we use only clientHeight here so aspect ratio does not distort speed // we use only clientHeight here so aspect ratio does not distort speed
panLeft(2 * deltaX * targetDistance / element.clientHeight, panLeft(2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix);
scope.object.matrix); panUp(2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix);
panUp(2 * deltaY * targetDistance / element.clientHeight,
scope.object.matrix);
} else if (scope.object.isOrthographicCamera) { } else if (scope.object.isOrthographicCamera) {
// orthographic // orthographic
panLeft(deltaX * (scope.object.right - scope.object.left) / panLeft(deltaX * (scope.object.right - scope.object.left) / scope.object.zoom / element.clientWidth, scope.object.matrix);
scope.object.zoom / element.clientWidth, scope.object.matrix); panUp(deltaY * (scope.object.top - scope.object.bottom) / scope.object.zoom / element.clientHeight, scope.object.matrix);
panUp(deltaY * (scope.object.top - scope.object.bottom) /
scope.object.zoom / element.clientHeight, scope.object.matrix);
} else unknownCamera(); } else {
unknownCamera();
} }
}() };
}();
function dollyIn(dollyScale) { function dollyIn(dollyScale) {
if (scope.object.isPerspectiveCamera) scale /= dollyScale; if (scope.object.isPerspectiveCamera) {
scale /= dollyScale;
else if (scope.object.isOrthographicCamera) { } else if (scope.object.isOrthographicCamera) {
scope.object.zoom = scope.object.zoom =
Math.max(scope.minZoom, Math.max(scope.minZoom,
Math.min(scope.maxZoom, scope.object.zoom * dollyScale)); Math.min(scope.maxZoom, scope.object.zoom * dollyScale));
scope.object.updateProjectionMatrix(); scope.object.updateProjectionMatrix();
zoomChanged = true; zoomChanged = true;
} else unknownCamera(); } else {
unknownCamera();
}
} }
function dollyOut(dollyScale) { function dollyOut(dollyScale) {
if (scope.object.isPerspectiveCamera) scale *= dollyScale; if (scope.object.isPerspectiveCamera) {
scale *= dollyScale;
else if (scope.object.isOrthographicCamera) { } else if (scope.object.isOrthographicCamera) {
scope.object.zoom = scope.object.zoom =
Math.max(scope.minZoom, Math.max(scope.minZoom,
Math.min(scope.maxZoom, scope.object.zoom / dollyScale)); Math.min(scope.maxZoom, scope.object.zoom / dollyScale));
scope.object.updateProjectionMatrix(); scope.object.updateProjectionMatrix();
zoomChanged = true; zoomChanged = true;
} else unknownCamera(); } else {
unknownCamera();
}
} }
// event callbacks - update the object state // event callbacks - update the object state
function handleMouseDownRotate(event) { function handleMouseDownRotate(event) {
rotateStart.set(event.clientX, event.clientY); rotateStart.set(event.clientX, event.clientY);
} }
function handleMouseDownDolly(event) { function handleMouseDownDolly(event) {
dollyStart.set(event.clientX, event.clientY); dollyStart.set(event.clientX, event.clientY);
} }
function handleMouseDownPan(event) { function handleMouseDownPan(event) {
panStart.set(event.clientX, event.clientY); panStart.set(event.clientX, event.clientY);
} }
function handleMouseMoveRotate(event) { function handleMouseMoveRotate(event) {
rotateEnd.set(event.clientX, event.clientY); rotateEnd.set(event.clientX, event.clientY);
rotateDelta.subVectors(rotateEnd, rotateStart) rotateDelta.subVectors(rotateEnd, rotateStart)
.multiplyScalar(scope.rotateSpeed); .multiplyScalar(scope.rotateSpeed);
var element = scope.domElement === document ? const element = scope.domElement === document ?
scope.domElement.body : scope.domElement; scope.domElement.body : scope.domElement;
// yes, height // yes, height
@@ -385,19 +380,20 @@ var OrbitControls = function (object, domElement) {
scope.update(); scope.update();
} }
function handleMouseMoveDolly(event) { function handleMouseMoveDolly(event) {
dollyEnd.set(event.clientX, event.clientY); dollyEnd.set(event.clientX, event.clientY);
dollyDelta.subVectors(dollyEnd, dollyStart); dollyDelta.subVectors(dollyEnd, dollyStart);
if (dollyDelta.y > 0) dollyIn(getZoomScale()); if (dollyDelta.y > 0) {
else if (dollyDelta.y < 0) dollyOut(getZoomScale()); dollyIn(getZoomScale());
} else if (dollyDelta.y < 0) {
dollyOut(getZoomScale());
}
dollyStart.copy(dollyEnd); dollyStart.copy(dollyEnd);
scope.update(); scope.update();
} }
function handleMouseMovePan(event) { function handleMouseMovePan(event) {
panEnd.set(event.clientX, event.clientY); panEnd.set(event.clientX, event.clientY);
panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed); panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed);
@@ -406,18 +402,16 @@ var OrbitControls = function (object, domElement) {
scope.update(); scope.update();
} }
function handleMouseUp(event) {}
function handleMouseWheel(event) { function handleMouseWheel(event) {
if (event.deltaY < 0) dollyOut(getZoomScale()); if (event.deltaY < 0) {
else if (event.deltaY > 0) dollyIn(getZoomScale()); dollyOut(getZoomScale());
} else if (event.deltaY > 0) {
dollyIn(getZoomScale());
}
scope.update(); scope.update();
} }
function handleKeyDown(event) { function handleKeyDown(event) {
switch (event.keyCode) { switch (event.keyCode) {
case scope.keys.UP: case scope.keys.UP:
@@ -442,35 +436,32 @@ var OrbitControls = function (object, domElement) {
} }
} }
function handleTouchStartRotate(event) { function handleTouchStartRotate(event) {
rotateStart.set(event.touches[0].pageX, event.touches[0].pageY); rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);
} }
function handleTouchStartDollyPan(event) { function handleTouchStartDollyPan(event) {
if (scope.enableZoom) { if (scope.enableZoom) {
var dx = event.touches[0].pageX - event.touches[1].pageX; const dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY; const dy = event.touches[0].pageY - event.touches[1].pageY;
var distance = Math.sqrt(dx * dx + dy * dy); const distance = Math.sqrt(dx * dx + dy * dy);
dollyStart.set(0, distance); dollyStart.set(0, distance);
} }
if (scope.enablePan) { if (scope.enablePan) {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX); const x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX);
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY); const y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY);
panStart.set(x, y); panStart.set(x, y);
} }
} }
function handleTouchMoveRotate(event) { function handleTouchMoveRotate(event) {
rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY); rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
rotateDelta.subVectors(rotateEnd, rotateStart) rotateDelta.subVectors(rotateEnd, rotateStart)
.multiplyScalar(scope.rotateSpeed); .multiplyScalar(scope.rotateSpeed);
var element = scope.domElement === document ? const element = scope.domElement === document ?
scope.domElement.body : scope.domElement; scope.domElement.body : scope.domElement;
// yes, height // yes, height
@@ -480,12 +471,11 @@ var OrbitControls = function (object, domElement) {
scope.update(); scope.update();
} }
function handleTouchMoveDollyPan(event) { function handleTouchMoveDollyPan(event) {
if (scope.enableZoom) { if (scope.enableZoom) {
var dx = event.touches[0].pageX - event.touches[1].pageX; const dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY; const dy = event.touches[0].pageY - event.touches[1].pageY;
var distance = Math.sqrt(dx * dx + dy * dy); const distance = Math.sqrt(dx * dx + dy * dy);
dollyEnd.set(0, distance); dollyEnd.set(0, distance);
dollyDelta.set(0, Math.pow(dollyEnd.y / dollyStart.y, scope.zoomSpeed)); dollyDelta.set(0, Math.pow(dollyEnd.y / dollyStart.y, scope.zoomSpeed));
@@ -493,10 +483,9 @@ var OrbitControls = function (object, domElement) {
dollyStart.copy(dollyEnd); dollyStart.copy(dollyEnd);
} }
if (scope.enablePan) { if (scope.enablePan) {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX); const x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX);
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY); const y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY);
panEnd.set(x, y); panEnd.set(x, y);
panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed); panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed);
@@ -507,82 +496,94 @@ var OrbitControls = function (object, domElement) {
scope.update(); scope.update();
} }
function handleTouchEnd(event) {}
// event handlers - listen for events and reset state // event handlers - listen for events and reset state
function onMouseDown(event) { function onMouseDown(event) {
if (!scope.enabled) return; if (!scope.enabled) {
return;
}
event.preventDefault(); event.preventDefault();
switch (event.button) { switch (event.button) {
case scope.mouseButtons.ORBIT: case scope.mouseButtons.ORBIT:
if (!scope.enableRotate) return; if (!scope.enableRotate) {
return;
}
handleMouseDownRotate(event); handleMouseDownRotate(event);
state = STATE.ROTATE; state = STATE.ROTATE;
break; break;
case scope.mouseButtons.ZOOM: case scope.mouseButtons.ZOOM:
if (!scope.enableZoom) return; if (!scope.enableZoom) {
return;
}
handleMouseDownDolly(event); handleMouseDownDolly(event);
state = STATE.DOLLY; state = STATE.DOLLY;
break; break;
case scope.mouseButtons.PAN: case scope.mouseButtons.PAN:
if (!scope.enablePan) return; if (!scope.enablePan) {
return;
}
handleMouseDownPan(event); handleMouseDownPan(event);
state = STATE.PAN; state = STATE.PAN;
break; break;
} }
if (state != STATE.NONE) { if (state != STATE.NONE) {
document.addEventListener('mousemove', onMouseMove, false); document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener('mouseup', onMouseUp, false); document.addEventListener("mouseup", onMouseUp, false);
scope.dispatchEvent(startEvent); scope.dispatchEvent(startEvent);
} }
} }
function onMouseMove(event) { function onMouseMove(event) {
if (!scope.enabled) return; if (!scope.enabled) {
return;
}
event.preventDefault(); event.preventDefault();
switch (state) { switch (state) {
case STATE.ROTATE: case STATE.ROTATE:
if (!scope.enableRotate) return; if (!scope.enableRotate) {
return;
}
handleMouseMoveRotate(event); handleMouseMoveRotate(event);
break; break;
case STATE.DOLLY: case STATE.DOLLY:
if (!scope.enableZoom) return; if (!scope.enableZoom) {
return;
}
handleMouseMoveDolly(event); handleMouseMoveDolly(event);
break; break;
case STATE.PAN: case STATE.PAN:
if (!scope.enablePan) return; if (!scope.enablePan) {
return;
}
handleMouseMovePan(event); handleMouseMovePan(event);
break; break;
} }
} }
function onMouseUp() {
if (!scope.enabled) {
return;
}
function onMouseUp(event) { document.removeEventListener("mousemove", onMouseMove, false);
if (!scope.enabled) return; document.removeEventListener("mouseup", onMouseUp, false);
handleMouseUp(event);
document.removeEventListener('mousemove', onMouseMove, false);
document.removeEventListener('mouseup', onMouseUp, false);
scope.dispatchEvent(endEvent); scope.dispatchEvent(endEvent);
state = STATE.NONE; state = STATE.NONE;
} }
function onMouseWheel(event) { function onMouseWheel(event) {
if (!scope.enabled || !scope.enableZoom || if (!scope.enabled || !scope.enableZoom ||
(state != STATE.NONE && state != STATE.ROTATE)) return; (state != STATE.NONE && state != STATE.ROTATE)) {
return;
}
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@@ -591,28 +592,34 @@ var OrbitControls = function (object, domElement) {
scope.dispatchEvent(endEvent); scope.dispatchEvent(endEvent);
} }
function onKeyDown(event) { function onKeyDown(event) {
if (!scope.enabled || !scope.enableKeys || !scope.enablePan) return; if (!scope.enabled || !scope.enableKeys || !scope.enablePan) {
return;
}
handleKeyDown(event); handleKeyDown(event);
} }
function onTouchStart(event) { function onTouchStart(event) {
if (!scope.enabled) return; if (!scope.enabled) {
return;
}
event.preventDefault(); event.preventDefault();
switch (event.touches.length) { switch (event.touches.length) {
case 1: // one-fingered touch: rotate case 1: // one-fingered touch: rotate
if (!scope.enableRotate) return; if (!scope.enableRotate) {
return;
}
handleTouchStartRotate(event); handleTouchStartRotate(event);
state = STATE.TOUCH_ROTATE; state = STATE.TOUCH_ROTATE;
break; break;
case 2: // two-fingered touch: dolly-pan case 2: // two-fingered touch: dolly-pan
if (!scope.enableZoom && !scope.enablePan) return; if (!scope.enableZoom && !scope.enablePan) {
return;
}
handleTouchStartDollyPan(event); handleTouchStartDollyPan(event);
state = STATE.TOUCH_DOLLY_PAN; state = STATE.TOUCH_DOLLY_PAN;
break; break;
@@ -620,27 +627,38 @@ var OrbitControls = function (object, domElement) {
default: state = STATE.NONE; default: state = STATE.NONE;
} }
if (state != STATE.NONE) scope.dispatchEvent(startEvent); if (state != STATE.NONE) {
scope.dispatchEvent(startEvent);
}
} }
function onTouchMove(event) { function onTouchMove(event) {
if (!scope.enabled) return; if (!scope.enabled) {
return;
}
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
switch (event.touches.length) { switch (event.touches.length) {
case 1: // one-fingered touch: rotate case 1: // one-fingered touch: rotate
if (!scope.enableRotate) return; if (!scope.enableRotate) {
if (state != STATE.TOUCH_ROTATE) return; // is this needed? return;
}
if (state != STATE.TOUCH_ROTATE) {
return;
} // is this needed?
handleTouchMoveRotate(event); handleTouchMoveRotate(event);
break; break;
case 2: // two-fingered touch: dolly-pan case 2: // two-fingered touch: dolly-pan
if (!scope.enableZoom && !scope.enablePan) return; if (!scope.enableZoom && !scope.enablePan) {
if (state != STATE.TOUCH_DOLLY_PAN) return; // is this needed? return;
}
if (state != STATE.TOUCH_DOLLY_PAN) {
return;
} // is this needed?
handleTouchMoveDollyPan(event); handleTouchMoveDollyPan(event);
break; break;
@@ -649,33 +667,32 @@ var OrbitControls = function (object, domElement) {
} }
} }
function onTouchEnd() {
if (!scope.enabled) {
return;
}
function onTouchEnd(event) {
if (!scope.enabled) return;
handleTouchEnd(event);
scope.dispatchEvent(endEvent); scope.dispatchEvent(endEvent);
state = STATE.NONE; state = STATE.NONE;
} }
function onContextMenu(event) { function onContextMenu(event) {
if (!scope.enabled) return; if (!scope.enabled) {
return;
}
event.preventDefault(); event.preventDefault();
} }
scope.domElement.addEventListener("contextmenu", onContextMenu, false);
scope.domElement.addEventListener('contextmenu', onContextMenu, false); scope.domElement.addEventListener("mousedown", onMouseDown, false);
scope.domElement.addEventListener('mousedown', onMouseDown, false); scope.domElement.addEventListener("wheel", onMouseWheel, false);
scope.domElement.addEventListener('wheel', onMouseWheel, false); scope.domElement.addEventListener("touchstart", onTouchStart, false);
scope.domElement.addEventListener('touchstart', onTouchStart, false); scope.domElement.addEventListener("touchend", onTouchEnd, false);
scope.domElement.addEventListener('touchend', onTouchEnd, false); scope.domElement.addEventListener("touchmove", onTouchMove, false);
scope.domElement.addEventListener('touchmove', onTouchMove, false); window.addEventListener("keydown", onKeyDown, false);
window .addEventListener('keydown', onKeyDown, false);
this.update(); // force an update at start this.update(); // force an update at start
} };
OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype); OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype);
OrbitControls.prototype.constructor = OrbitControls; OrbitControls.prototype.constructor = OrbitControls;

View File

@@ -1,58 +1,32 @@
/******************************************************************************\ "use strict";
Copyright 2018. Buildbotics LLC const orbit = require("./orbit");
All Rights Reserved. const cookie = require("./cookie")("bbctrl-");
const font = require("./helvetiker_regular.typeface.json");
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 font = require('./helvetiker_regular.typeface.json')
module.exports = { module.exports = {
template: '#path-viewer-template', template: "#path-viewer-template",
props: ['toolpath'], props: [ "toolpath" ],
data: function() { data: function() {
return { return {
enabled: false, enabled: false,
loading: false, loading: false,
dirty: true, dirty: true,
snapView: cookie.get('snap-view', 'isometric'), snapView: cookie.get("snap-view", "isometric"),
small: cookie.get_bool('small-path-view', true), small: cookie.get_bool("small-path-view", true),
surfaceMode: 'cut', surfaceMode: "cut",
showPath: cookie.get_bool('show-path', true), showPath: cookie.get_bool("show-path", true),
showTool: cookie.get_bool('show-tool', true), showTool: cookie.get_bool("show-tool", true),
showBBox: cookie.get_bool('show-bbox', true), showBBox: cookie.get_bool("show-bbox", true),
showAxes: cookie.get_bool('show-axes', true), showAxes: cookie.get_bool("show-axes", true),
showIntensity: cookie.get_bool('show-intensity', false) showIntensity: cookie.get_bool("show-intensity", false)
} };
}, },
computed: { computed: {
target: function() { target: function() {
return $(this.$el).find('.path-viewer-content')[0] return this.$el.querySelector(".path-viewer-content");
}, },
webglAvailable: function() { webglAvailable: function() {
@@ -69,61 +43,64 @@ module.exports = {
} }
}, },
watch: { watch: {
toolpath: function () {Vue.nextTick(this.update)}, toolpath: function() {
surfaceMode: function (mode) {this.update_surface_mode(mode)}, Vue.nextTick(this.update);
},
surfaceMode: function(mode) {
this.update_surface_mode(mode);
},
small: function(enable) { small: function(enable) {
cookie.set_bool('small-path-view', enable); cookie.set_bool("small-path-view", enable);
Vue.nextTick(this.update_view) Vue.nextTick(this.update_view);
}, },
showPath: function(enable) { showPath: function(enable) {
cookie.set_bool('show-path', enable); cookie.set_bool("show-path", enable);
this.set_visible(this.pathView, enable) this.set_visible(this.pathView, enable);
}, },
showTool: function(enable) { showTool: function(enable) {
cookie.set_bool('show-tool', enable); cookie.set_bool("show-tool", enable);
this.set_visible(this.toolView, enable) this.set_visible(this.toolView, enable);
}, },
showAxes: function(enable) { showAxes: function(enable) {
cookie.set_bool('show-axes', enable); cookie.set_bool("show-axes", enable);
this.set_visible(this.axesView, enable) this.set_visible(this.axesView, enable);
}, },
showIntensity: function(enable) { showIntensity: function(enable) {
cookie.set_bool('show-intensity', enable); cookie.set_bool("show-intensity", enable);
Vue.nextTick(this.update) Vue.nextTick(this.update);
}, },
showBBox: function(enable) { showBBox: function(enable) {
cookie.set_bool('show-bbox', enable); cookie.set_bool("show-bbox", enable);
this.set_visible(this.bboxView, enable); this.set_visible(this.bboxView, enable);
this.set_visible(this.envelopeView, enable); this.set_visible(this.envelopeView, enable);
}, },
x: function() {
x: function () {this.axis_changed()}, this.axis_changed();
y: function () {this.axis_changed()},
z: function () {this.axis_changed()}
}, },
y: function() {
this.axis_changed();
},
z: function() {
this.axis_changed();
}
},
ready: function() { ready: function() {
this.graphics(); this.graphics();
Vue.nextTick(this.update); Vue.nextTick(this.update);
}, },
methods: { methods: {
update: async function() { update: async function() {
if (!this.webglAvailable) { if (!this.webglAvailable) {
@@ -140,21 +117,23 @@ module.exports = {
this.draw_loading(); this.draw_loading();
} }
if (!this.enabled || !this.toolpath.filename) return; if (!this.enabled || !this.toolpath.filename) {
return;
}
async function get(url) { async function get(url) {
const response = await fetch(`${url}?${Math.random()}`); const response = await fetch(`${url}`, { cache: "no-cache" });
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer();
return new Float32Array(arrayBuffer); return new Float32Array(arrayBuffer);
} }
const [ positions, speeds ] = await Promise.all([ const [ positions, speeds ] = await Promise.all([
get('/api/path/' + this.toolpath.filename + '/positions'), get(`/api/path/${this.toolpath.filename}/positions`),
get('/api/path/' + this.toolpath.filename + '/speeds') get(`/api/path/${this.toolpath.filename}/speeds`)
]); ]);
this.positions = positions this.positions = positions;
this.speeds = speeds; this.speeds = speeds;
this.loading = false; this.loading = false;
@@ -166,22 +145,22 @@ module.exports = {
this.update_view(); this.update_view();
}, },
update_surface_mode: function(mode) { update_surface_mode: function(mode) {
if (!this.enabled) return; if (!this.enabled) {
return;
}
if (typeof this.surfaceMaterial != 'undefined') { if (typeof this.surfaceMaterial != "undefined") {
this.surfaceMaterial.wireframe = mode == 'wire'; this.surfaceMaterial.wireframe = mode == "wire";
this.surfaceMaterial.needsUpdate = true; this.surfaceMaterial.needsUpdate = true;
} }
this.set_visible(this.surfaceMesh, mode == 'cut' || mode == 'wire'); this.set_visible(this.surfaceMesh, mode == "cut" || mode == "wire");
this.set_visible(this.workpieceMesh, mode == 'solid'); this.set_visible(this.workpieceMesh, mode == "solid");
}, },
load_surface: function(surface) { load_surface: function(surface) {
if (typeof surface == 'undefined') { if (typeof surface == "undefined") {
this.vertices = undefined; this.vertices = undefined;
this.normals = undefined; this.normals = undefined;
return; return;
@@ -191,30 +170,37 @@ module.exports = {
// Expand normals // Expand normals
this.normals = []; this.normals = [];
for (var i = 0; i < surface.normals.length / 3; i++) for (let i = 0; i < surface.normals.length / 3; i++) {
for (var j = 0; j < 3; j++) for (let j = 0; j < 3; j++) {
for (var k = 0; k < 3; k++) for (let k = 0; k < 3; k++) {
this.normals.push(surface.normals[i * 3 + k]); this.normals.push(surface.normals[i * 3 + k]);
}
}
}
}, },
set_visible: function(target, visible) { set_visible: function(target, visible) {
if (typeof target != 'undefined') target.visible = visible; if (typeof target != "undefined") {
target.visible = visible;
}
this.dirty = true; this.dirty = true;
}, },
get_dims: function() { get_dims: function() {
var t = $(this.target); const computedStyle = window.getComputedStyle(this.target);
var width = t.innerWidth();
var height = t.innerHeight(); return {
return {width: width, height: height}; width: parseInt(computedStyle.width),
height: parseInt(computedStyle.height)
};
}, },
update_view: function() { update_view: function() {
if (!this.enabled) return; if (!this.enabled) {
var dims = this.get_dims(); return;
}
const dims = this.get_dims();
this.camera.aspect = dims.width / dims.height; this.camera.aspect = dims.width / dims.height;
this.camera.updateProjectionMatrix(); this.camera.updateProjectionMatrix();
@@ -229,47 +215,59 @@ module.exports = {
this.dirty = true; this.dirty = true;
}, },
update_tool: function(tool) { update_tool: function(tool) {
if (!this.enabled) return; if (!this.enabled) {
if (typeof tool == 'undefined') tool = this.toolView; return;
if (typeof tool == 'undefined') return; }
if (typeof tool == "undefined") {
tool = this.toolView;
}
if (typeof tool == "undefined") {
return;
}
tool.position.x = this.x.pos; tool.position.x = this.x.pos;
tool.position.y = this.y.pos; tool.position.y = this.y.pos;
tool.position.z = this.z.pos; tool.position.z = this.z.pos;
}, },
update_envelope: function(envelope) { update_envelope: function(envelope) {
if (!this.enabled || !this.axes.homed) return; if (!this.enabled || !this.axes.homed) {
if (typeof envelope == 'undefined') envelope = this.envelopeView; return;
if (typeof envelope == 'undefined') return; }
var min = new THREE.Vector3(); if (typeof envelope == "undefined") {
var max = new THREE.Vector3(); envelope = this.envelopeView;
}
for (var axis of 'xyz') { if (typeof envelope == "undefined") {
return;
}
const min = new THREE.Vector3();
const max = new THREE.Vector3();
for (const axis of "xyz") {
min[axis] = this[axis].min - this[axis].off; min[axis] = this[axis].min - this[axis].off;
max[axis] = this[axis].max - this[axis].off; max[axis] = this[axis].max - this[axis].off;
} }
var bounds = new THREE.Box3(min, max); const bounds = new THREE.Box3(min, max);
if (bounds.isEmpty()) { if (bounds.isEmpty()) {
envelope.geometry = this.create_empty_geom(); envelope.geometry = this.create_empty_geom();
} } else {
else {
envelope.geometry = this.create_bbox_geom(bounds); envelope.geometry = this.create_bbox_geom(bounds);
} }
}, },
axis_changed: function() { axis_changed: function() {
this.update_tool(); this.update_tool();
this.update_envelope(); this.update_envelope();
this.dirty = true; this.dirty = true;
}, },
graphics: function() { graphics: function() {
if (!this.webglAvailable) { if (!this.webglAvailable) {
return; return;
@@ -283,7 +281,7 @@ module.exports = {
this.target.appendChild(this.renderer.domElement); this.target.appendChild(this.renderer.domElement);
} catch (e) { } catch (e) {
console.log('WebGL not supported: ', e); console.log("WebGL not supported: ", e);
return; return;
} }
@@ -295,15 +293,13 @@ module.exports = {
// Lighting // Lighting
this.ambient = new THREE.AmbientLight(0xffffff, 0.5); this.ambient = new THREE.AmbientLight(0xffffff, 0.5);
var keyLight = new THREE.DirectionalLight const keyLight = new THREE.DirectionalLight(new THREE.Color("hsl(30, 100%, 75%)"), 0.75);
(new THREE.Color('hsl(30, 100%, 75%)'), 0.75);
keyLight.position.set(-100, 0, 100); keyLight.position.set(-100, 0, 100);
var fillLight = new THREE.DirectionalLight const fillLight = new THREE.DirectionalLight(new THREE.Color("hsl(240, 100%, 75%)"), 0.25);
(new THREE.Color('hsl(240, 100%, 75%)'), 0.25);
fillLight.position.set(100, 0, 100); fillLight.position.set(100, 0, 100);
var backLight = new THREE.DirectionalLight(0xffffff, 0.5); const backLight = new THREE.DirectionalLight(0xffffff, 0.5);
backLight.position.set(100, 0, -100).normalize(); backLight.position.set(100, 0, -100).normalize();
this.lights = new THREE.Group(); this.lights = new THREE.Group();
@@ -322,7 +318,7 @@ module.exports = {
this.controls.enableZoom = true; this.controls.enableZoom = true;
// Move lights with scene // Move lights with scene
this.controls.addEventListener('change', function (scope) { this.controls.addEventListener("change", function(scope) {
return function() { return function() {
keyLight.position.copy(scope.camera.position); keyLight.position.copy(scope.camera.position);
fillLight.position.copy(scope.camera.position); fillLight.position.copy(scope.camera.position);
@@ -331,17 +327,16 @@ module.exports = {
fillLight.lookAt(scope.controls.target); fillLight.lookAt(scope.controls.target);
backLight.lookAt(scope.controls.target); backLight.lookAt(scope.controls.target);
scope.dirty = true; scope.dirty = true;
} };
}(this)) }(this));
// Events // Events
window.addEventListener('resize', this.update_view, false); window.addEventListener("resize", this.update_view, false);
// Start it // Start it
this.render(); this.render();
}, },
create_surface_material: function() { create_surface_material: function() {
return new THREE.MeshPhongMaterial({ return new THREE.MeshPhongMaterial({
specular: 0x111111, specular: 0x111111,
@@ -351,11 +346,10 @@ module.exports = {
}); });
}, },
draw_loading: function() { draw_loading: function() {
this.scene = new THREE.Scene(); this.scene = new THREE.Scene();
var geometry = new THREE.TextGeometry('Loading 3D View...', { const geometry = new THREE.TextGeometry("Loading 3D View...", {
font: new THREE.Font(font), font: new THREE.Font(font),
size: 40, size: 40,
height: 5, height: 5,
@@ -367,8 +361,7 @@ module.exports = {
}); });
geometry.computeBoundingBox(); geometry.computeBoundingBox();
var center = geometry.center(); const mesh = new THREE.Mesh(geometry, this.surfaceMaterial);
var mesh = new THREE.Mesh(geometry, this.surfaceMaterial);
this.scene.add(mesh); this.scene.add(mesh);
this.scene.add(this.ambient); this.scene.add(this.ambient);
@@ -376,21 +369,22 @@ module.exports = {
this.update_view(); this.update_view();
}, },
draw_workpiece: function(scene, material) { draw_workpiece: function(scene, material) {
if (typeof this.workpiece == 'undefined') return; if (typeof this.workpiece == "undefined") {
return;
}
var min = this.workpiece.min; let min = this.workpiece.min;
var max = this.workpiece.max; let max = this.workpiece.max;
min = new THREE.Vector3(min[0], min[1], min[2]); min = new THREE.Vector3(min[0], min[1], min[2]);
max = new THREE.Vector3(max[0], max[1], max[2]); max = new THREE.Vector3(max[0], max[1], max[2]);
var dims = max.clone().sub(min); const dims = max.clone().sub(min);
var geometry = new THREE.BoxGeometry(dims.x, dims.y, dims.z) const geometry = new THREE.BoxGeometry(dims.x, dims.y, dims.z);
var mesh = new THREE.Mesh(geometry, material); const mesh = new THREE.Mesh(geometry, material);
var offset = dims.clone(); const offset = dims.clone();
offset.divideScalar(2); offset.divideScalar(2);
offset.add(min); offset.add(min);
@@ -403,16 +397,15 @@ module.exports = {
return mesh; return mesh;
}, },
draw_surface: function(scene, material) { draw_surface: function(scene, material) {
if (typeof this.vertices == 'undefined') return; if (typeof this.vertices == "undefined") {
return;
}
var geometry = new THREE.BufferGeometry(); const geometry = new THREE.BufferGeometry();
geometry.addAttribute geometry.addAttribute("position", new THREE.Float32BufferAttribute(this.vertices, 3));
('position', new THREE.Float32BufferAttribute(this.vertices, 3)); geometry.addAttribute("normal", new THREE.Float32BufferAttribute(this.normals, 3));
geometry.addAttribute
('normal', new THREE.Float32BufferAttribute(this.normals, 3));
geometry.computeBoundingSphere(); geometry.computeBoundingSphere();
geometry.computeBoundingBox(); geometry.computeBoundingBox();
@@ -420,15 +413,16 @@ module.exports = {
return new THREE.Mesh(geometry, material); return new THREE.Mesh(geometry, material);
}, },
draw_tool: function(scene, bbox) { draw_tool: function(scene, bbox) {
// Tool size is relative to bounds // Tool size is relative to bounds
var size = bbox.getSize(new THREE.Vector3()); const size = bbox.getSize(new THREE.Vector3());
var length = (size.x + size.y + size.z) / 24; let length = (size.x + size.y + size.z) / 24;
if (length < 1) length = 1; if (length < 1) {
length = 1;
}
var material = new THREE.MeshPhongMaterial({ const material = new THREE.MeshPhongMaterial({
transparent: true, transparent: true,
opacity: 0.75, opacity: 0.75,
specular: 0x161616, specular: 0x161616,
@@ -436,30 +430,33 @@ module.exports = {
color: 0xffa500 // Orange color: 0xffa500 // Orange
}); });
var geometry = new THREE.CylinderGeometry(length / 2, 0, length, 128); const geometry = new THREE.CylinderGeometry(length / 2, 0, length, 128);
geometry.translate(0, length / 2, 0); geometry.translate(0, length / 2, 0);
geometry.rotateX(0.5 * Math.PI); geometry.rotateX(0.5 * Math.PI);
var mesh = new THREE.Mesh(geometry, material); const mesh = new THREE.Mesh(geometry, material);
this.update_tool(mesh); this.update_tool(mesh);
mesh.visible = this.showTool; mesh.visible = this.showTool;
scene.add(mesh); scene.add(mesh);
return mesh; return mesh;
}, },
draw_axis: function(axis, up, length, radius) { draw_axis: function(axis, up, length, radius) {
var color; let color;
if (axis == 0) color = 0xff0000; // Red if (axis == 0) {
else if (axis == 1) color = 0x00ff00; // Green color = 0xff0000;
else if (axis == 2) color = 0x0000ff; // Blue } else if (axis == 1) {
color = 0x00ff00;
} else if (axis == 2) {
color = 0x0000ff;
}
var group = new THREE.Group(); const group = new THREE.Group();
var material = new THREE.MeshPhongMaterial({ const material = new THREE.MeshPhongMaterial({
specular: 0x161616, shininess: 10, color: color specular: 0x161616, shininess: 10, color: color
}); });
var geometry = new THREE.CylinderGeometry(radius, radius, length, 128); let geometry = new THREE.CylinderGeometry(radius, radius, length, 128);
geometry.translate(0, -length / 2, 0); geometry.translate(0, -length / 2, 0);
group.add(new THREE.Mesh(geometry, material)); group.add(new THREE.Mesh(geometry, material));
@@ -467,28 +464,35 @@ module.exports = {
geometry.translate(0, -length - radius, 0); geometry.translate(0, -length - radius, 0);
group.add(new THREE.Mesh(geometry, material)); group.add(new THREE.Mesh(geometry, material));
if (axis == 0) group.rotateZ((up ? 0.5 : 1.5) * Math.PI); if (axis == 0) {
else if (axis == 1) group.rotateX((up ? 0 : 1 ) * Math.PI); group.rotateZ((up ? 0.5 : 1.5) * Math.PI);
else if (axis == 2) group.rotateX((up ? 1.5 : 0.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; return group;
}, },
draw_axes: function(scene, bbox) { draw_axes: function(scene, bbox) {
var size = bbox.getSize(new THREE.Vector3()); const size = bbox.getSize(new THREE.Vector3());
var length = (size.x + size.y + size.z) / 3; let length = (size.x + size.y + size.z) / 3;
length /= 10; length /= 10;
if (length < 1) length = 1; if (length < 1) {
length = 1;
}
var radius = length / 20; const radius = length / 20;
var group = new THREE.Group(); const group = new THREE.Group();
for (var axis = 0; axis < 3; axis++) for (let axis = 0; axis < 3; axis++) {
for (var up = 0; up < 2; up++) for (let up = 0; up < 2; up++) {
group.add(this.draw_axis(axis, up, length, radius)); group.add(this.draw_axis(axis, up, length, radius));
}
}
group.visible = this.showAxes; group.visible = this.showAxes;
scene.add(group); scene.add(group);
@@ -496,40 +500,42 @@ module.exports = {
return group; return group;
}, },
get_color: function(speed) { get_color: function(speed) {
if (isNaN(speed)) return [255, 0, 0]; // Rapid if (isNaN(speed)) {
return [ 255, 0, 0 ];
} // Rapid
let intensity = speed / this.toolpath.maxSpeed;
if (typeof speed == "undefined" || !this.showIntensity) {
intensity = 1;
}
var intensity = speed / this.toolpath.maxSpeed;
if (typeof speed == 'undefined' || !this.showIntensity) intensity = 1;
return [ 0, 255 * intensity, 127 * (1 - intensity) ]; return [ 0, 255 * intensity, 127 * (1 - intensity) ];
}, },
draw_path: function(scene) { draw_path: function(scene) {
var geometry = new THREE.BufferGeometry(); const geometry = new THREE.BufferGeometry();
var material = const material = new THREE.LineBasicMaterial({
new THREE.LineBasicMaterial({
vertexColors: THREE.VertexColors, vertexColors: THREE.VertexColors,
linewidth: 1.5 linewidth: 1.5
}); });
var positions = new THREE.Float32BufferAttribute(this.positions, 3); const positions = new THREE.Float32BufferAttribute(this.positions, 3);
geometry.addAttribute('position', positions); geometry.addAttribute("position", positions);
var colors = []; let colors = [];
for (var i = 0; i < this.speeds.length; i++) { for (let i = 0; i < this.speeds.length; i++) {
var color = this.get_color(this.speeds[i]); const color = this.get_color(this.speeds[i]);
Array.prototype.push.apply(colors, color); Array.prototype.push.apply(colors, color);
} }
colors = new THREE.Uint8BufferAttribute(colors, 3, true); colors = new THREE.Uint8BufferAttribute(colors, 3, true);
geometry.addAttribute('color', colors); geometry.addAttribute("color", colors);
geometry.computeBoundingSphere(); geometry.computeBoundingSphere();
geometry.computeBoundingBox(); geometry.computeBoundingBox();
var line = new THREE.Line(geometry, material); const line = new THREE.Line(geometry, material);
line.visible = this.showPath; line.visible = this.showPath;
scene.add(line); scene.add(line);
@@ -537,17 +543,15 @@ module.exports = {
return line; return line;
}, },
create_empty_geom: function() { create_empty_geom: function() {
var geometry = new THREE.BufferGeometry(); const geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', geometry.addAttribute("position",
new THREE.Float32BufferAttribute([], 3)); new THREE.Float32BufferAttribute([], 3));
return geometry; return geometry;
}, },
create_bbox_geom: function(bbox) { create_bbox_geom: function(bbox) {
var vertices = []; const vertices = [];
if (!bbox.isEmpty()) { if (!bbox.isEmpty()) {
// Top // Top
@@ -581,19 +585,18 @@ module.exports = {
vertices.push(bbox.min.x, bbox.max.y, bbox.max.z); vertices.push(bbox.min.x, bbox.max.y, bbox.max.z);
} }
var geometry = new THREE.BufferGeometry(); const geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', geometry.addAttribute("position",
new THREE.Float32BufferAttribute(vertices, 3)); new THREE.Float32BufferAttribute(vertices, 3));
return geometry; return geometry;
}, },
draw_bbox: function(scene, bbox) { draw_bbox: function(scene, bbox) {
var geometry = this.create_bbox_geom(bbox); const geometry = this.create_bbox_geom(bbox);
var material = new THREE.LineBasicMaterial({color: 0xffffff}); const material = new THREE.LineBasicMaterial({ color: 0xffffff });
var line = new THREE.LineSegments(geometry, material); const line = new THREE.LineSegments(geometry, material);
line.visible = this.showBBox; line.visible = this.showBBox;
@@ -602,11 +605,10 @@ module.exports = {
return line; return line;
}, },
draw_envelope: function(scene) { draw_envelope: function(scene) {
var geometry = this.create_empty_geom(); const geometry = this.create_empty_geom();
var material = new THREE.LineBasicMaterial({color: 0x00f7ff}); const material = new THREE.LineBasicMaterial({ color: 0x00f7ff });
var line = new THREE.LineSegments(geometry, material); const line = new THREE.LineSegments(geometry, material);
line.visible = this.showBBox; line.visible = this.showBBox;
@@ -616,7 +618,6 @@ module.exports = {
return line; return line;
}, },
draw: function(scene) { draw: function(scene) {
// Lights // Lights
scene.add(this.ambient); scene.add(this.ambient);
@@ -629,7 +630,7 @@ module.exports = {
this.update_surface_mode(this.surfaceMode); this.update_surface_mode(this.surfaceMode);
// Compute bounding box // Compute bounding box
var bbox = this.get_model_bounds(); const bbox = this.get_model_bounds();
// Tool, axes & bounds // Tool, axes & bounds
this.toolView = this.draw_tool(scene, bbox); this.toolView = this.draw_tool(scene, bbox);
@@ -638,10 +639,12 @@ module.exports = {
this.envelopeView = this.draw_envelope(scene); this.envelopeView = this.draw_envelope(scene);
}, },
render: function() { render: function() {
window.requestAnimationFrame(this.render); window.requestAnimationFrame(this.render);
if (typeof this.scene == 'undefined') return;
if (typeof this.scene == "undefined") {
return;
}
if (this.controls.update() || this.dirty) { if (this.controls.update() || this.dirty) {
this.dirty = false; this.dirty = false;
@@ -649,14 +652,13 @@ module.exports = {
} }
}, },
get_model_bounds: function() { get_model_bounds: function() {
var bbox = new THREE.Box3(new THREE.Vector3(0, 0, 0), const bbox = new THREE.Box3(new THREE.Vector3(0, 0, 0),
new THREE.Vector3(0.00001, 0.00001, 0.00001)); new THREE.Vector3(0.00001, 0.00001, 0.00001));
function add(o) { function add(o) {
if (typeof o != 'undefined') { if (typeof o != "undefined") {
var oBBox = new THREE.Box3(); const oBBox = new THREE.Box3();
oBBox.setFromObject(o); oBBox.setFromObject(o);
bbox.union(oBBox); bbox.union(oBBox);
} }
@@ -669,45 +671,53 @@ module.exports = {
return bbox; return bbox;
}, },
snap: function(view) { snap: function(view) {
if (this.loading) return; if (this.loading) {
if (view != this.snapView) { return;
this.snapView = view;
cookie.set('snap-view', view);
} }
var bbox = this.get_model_bounds(); if (view != this.snapView) {
this.snapView = view;
cookie.set("snap-view", view);
}
const bbox = this.get_model_bounds();
this.controls.reset(); this.controls.reset();
bbox.getCenter(this.controls.target); bbox.getCenter(this.controls.target);
this.update_view(); this.update_view();
// Compute new camera position // Compute new camera position
var center = bbox.getCenter(new THREE.Vector3()); const center = bbox.getCenter(new THREE.Vector3());
var offset = new THREE.Vector3(); const offset = new THREE.Vector3();
switch (view) {
case "isometric": offset.y -= 1; offset.z += 1; break;
case "front": offset.y -= 1; break;
case "back": offset.y += 1; break;
case "left": offset.x -= 1; break;
case "right": offset.x += 1; break;
case "top": offset.z += 1; break;
case "bottom": offset.z -= 1; break;
}
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(); offset.normalize();
// Initial camera position // Initial camera position
var position = new THREE.Vector3().copy(center).add(offset); const position = new THREE.Vector3().copy(center).add(offset);
this.camera.position.copy(position); this.camera.position.copy(position);
this.camera.lookAt(center); // Get correct camera orientation this.camera.lookAt(center); // Get correct camera orientation
var theta = this.camera.fov / 180 * Math.PI; // View angle const theta = this.camera.fov / 180 * Math.PI; // View angle
var cameraLine = new THREE.Line3(center, position); const cameraLine = new THREE.Line3(center, position);
var cameraUp = new THREE.Vector3().copy(this.camera.up) const cameraUp = new THREE.Vector3()
.copy(this.camera.up)
.applyQuaternion(this.camera.quaternion); .applyQuaternion(this.camera.quaternion);
var cameraLeft = const cameraLeft = new THREE.Vector3()
new THREE.Vector3().copy(offset).cross(cameraUp).normalize(); .copy(offset)
.cross(cameraUp)
.normalize();
var corners = [ const corners = [
new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.min.z), 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.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.min.z),
@@ -716,39 +726,37 @@ module.exports = {
new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.max.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.min.z),
new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.max.z), new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.max.z),
] ];
var dist = this.camera.near; // Min camera dist let dist = this.camera.near; // Min camera dist
for (var i = 0; i < corners.length; i++) { for (let i = 0; i < corners.length; i++) {
// Project on to camera line // Project on to camera line
var p1 = cameraLine const p1 = cameraLine.closestPointToPoint(corners[i], false, new THREE.Vector3());
.closestPointToPoint(corners[i], false, new THREE.Vector3());
// Compute distance from projection to center // Compute distance from projection to center
var d = p1.distanceTo(center); let d = p1.distanceTo(center);
if (cameraLine.closestPointToPointParameter(p1, false) < 0) d = -d; if (cameraLine.closestPointToPointParameter(p1, false) < 0) {
d = -d;
}
// Compute up line // Compute up line
var up = const up = new THREE.Line3(p1, new THREE.Vector3().copy(p1).add(cameraUp));
new THREE.Line3(p1, new THREE.Vector3().copy(p1).add(cameraUp));
// Project on to up line // Project on to up line
var p2 = up.closestPointToPoint(corners[i], false, new THREE.Vector3()); const p2 = up.closestPointToPoint(corners[i], false, new THREE.Vector3());
// Compute length // Compute length
var l = p1.distanceTo(p2); let l = p1.distanceTo(p2);
// Update min camera distance // Update min camera distance
dist = Math.max(dist, d + l / Math.tan(theta / 2)); dist = Math.max(dist, d + l / Math.tan(theta / 2));
// Compute left line // Compute left line
var left = const left = new THREE.Line3(p1, new THREE.Vector3().copy(p1).add(cameraLeft));
new THREE.Line3(p1, new THREE.Vector3().copy(p1).add(cameraLeft));
// Project on to left line // Project on to left line
var p3 = const p3 = left.closestPointToPoint(corners[i], false, new THREE.Vector3());
left.closestPointToPoint(corners[i], false, new THREE.Vector3());
// Compute length // Compute length
l = p1.distanceTo(p3); l = p1.distanceTo(p3);
@@ -761,6 +769,5 @@ module.exports = {
} }
}, },
mixins: [ require("./axis-vars") ]
mixins: [require('./axis-vars')] };
}

View File

@@ -1,42 +1,14 @@
/******************************************************************************\
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 = { module.exports = {
template: '#settings-view-template', template: "#settings-view-template",
props: ['config', 'template'],
attached: function() {
this.svelteComponent = SvelteComponents.createComponent(
"SettingsView",
document.getElementById("settings")
);
},
events: { detached: function() {
'input-changed': function() { this.svelteComponent.$destroy();
this.$dispatch('config-changed');
return false;
}
}
} }
};

View File

@@ -1,38 +1,16 @@
/******************************************************************************\ "use strict";
This file is part of the Buildbotics firmware. const Sock = function(url, retry, timeout) {
if (!(this instanceof Sock)) {
return new Sock(url, retry);
}
Copyright (c) 2015 - 2018, Buildbotics LLC if (typeof retry == "undefined") {
All rights reserved. retry = 2000;
}
This file ("the software") is free software: you can redistribute it if (typeof timeout == "undefined") {
and/or modify it under the terms of the GNU General Public License, timeout = 16000;
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.url = url;
this.retry = retry; this.retry = retry;
@@ -41,87 +19,88 @@ var Sock = function (url, retry, timeout) {
this.count = 0; this.count = 0;
this.connect(); this.connect();
} };
Sock.prototype.onmessage = function() {
// Ignore
};
Sock.prototype.onmessage = function () {} Sock.prototype.onopen = function() {
Sock.prototype.onopen = function () {} // Ignore
Sock.prototype.onclose = function () {} };
Sock.prototype.onclose = function() {
// Ignore
};
Sock.prototype.connect = function() { Sock.prototype.connect = function() {
console.debug('connecting to', this.url); console.debug("connecting to", this.url);
this.close(); this.close();
this._sock = new SockJS(this.url); this._sock = new SockJS(this.url);
this._sock.onmessage = function(e) { this._sock.onmessage = function(e) {
console.debug('msg:', e.data); console.debug("msg:", e.data);
this.heartbeat('msg'); this.heartbeat("msg");
this.onmessage(e); this.onmessage(e);
}.bind(this); }.bind(this);
this._sock.onopen = function() { this._sock.onopen = function() {
console.debug('connected'); console.debug("connected");
this.heartbeat('open'); this.heartbeat("open");
this.onopen(); this.onopen();
}.bind(this); }.bind(this);
this._sock.onclose = function() { this._sock.onclose = function() {
console.debug('disconnected'); console.debug("disconnected");
this._cancel_timeout(); this._cancel_timeout();
this.onclose(); this.onclose();
if (typeof this._sock != 'undefined') if (typeof this._sock != "undefined") {
setTimeout(this.connect.bind(this), this.retry); setTimeout(this.connect.bind(this), this.retry);
}.bind(this);
} }
}.bind(this);
};
Sock.prototype._timedout = function() { Sock.prototype._timedout = function() {
// Divide timeout so slow browser doesn't trigger timeouts when the // Divide timeout so slow browser doesn't trigger timeouts when the
// connection is good. // connection is good.
if (this.divisions <= ++this.count) { if (this.divisions <= ++this.count) {
console.debug('connection timedout'); console.debug("connection timedout");
this._timeout = undefined; this._timeout = undefined;
this._sock.close(); this._sock.close();
} else this._set_timeout(); } else {
this._set_timeout();
} }
};
Sock.prototype._cancel_timeout = function() { Sock.prototype._cancel_timeout = function() {
clearTimeout(this._timeout); clearTimeout(this._timeout);
this._timeout = undefined; this._timeout = undefined;
this.count = 0; this.count = 0;
} };
Sock.prototype._set_timeout = function() { Sock.prototype._set_timeout = function() {
this._timeout = setTimeout(this._timedout.bind(this), this._timeout = setTimeout(this._timedout.bind(this),
this.timeout / this.divisions); this.timeout / this.divisions);
} };
Sock.prototype.heartbeat = function() {
Sock.prototype.heartbeat = function (msg) {
//console.debug('heartbeat ' + new Date().toLocaleTimeString() + ' ' + msg);
this._cancel_timeout(); this._cancel_timeout();
this._set_timeout(); this._set_timeout();
} };
Sock.prototype.close = function() { Sock.prototype.close = function() {
if (typeof this._sock != 'undefined') { if (typeof this._sock != "undefined") {
var sock = this._sock; const sock = this._sock;
this._sock = undefined; this._sock = undefined;
sock.close(); sock.close();
} }
} };
Sock.prototype.send = function(msg) {
this._sock.send(msg);
};
Sock.prototype.send = function (msg) {this._sock.send(msg)} module.exports = Sock;
module.exports = Sock

View File

@@ -1,49 +1,24 @@
/******************************************************************************\ "use strict";
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 = { module.exports = {
replace: true, replace: true,
template: '#templated-input-template', template: "#templated-input-template",
props: ['name', 'model', 'template'], props: [ "name", "model", "template" ],
data: function () {return {view: ''}},
data: function() {
return { view: "" };
},
computed: { computed: {
metric: function () {return this.$root.metric()}, metric: function() {
return this.$root.display_units === "METRIC";
},
_view: function() { _view: function() {
if (this.template.scale) { if (this.template.scale) {
if (this.metric) return 1 * this.model.toFixed(3); if (this.metric) {
return 1 * this.model.toFixed(3);
}
return 1 * (this.model / this.template.scale).toFixed(4); return 1 * (this.model / this.template.scale).toFixed(4);
} }
@@ -51,39 +26,44 @@ module.exports = {
return this.model; return this.model;
}, },
units: function() { units: function() {
return (this.metric || !this.template.iunit) ? return (this.metric || !this.template.iunit)
this.template.unit : this.template.iunit; ? this.template.unit
: this.template.iunit;
}, },
title: function() { title: function() {
var s = 'Default ' + this.template.default + ' ' + let s = `Default :${this.template.default} ${(this.template.unit || "")}`;
(this.template.unit || '');
if (typeof this.template.help != 'undefined') if (typeof this.template.help != "undefined") {
s = this.template.help + '\n' + s; s = `${this.template.help}\n${s}`;
}
return s; return s;
} }
}, },
watch: { watch: {
_view: function () {this.view = this._view}, _view: function() {
this.view = this._view;
},
view: function() { view: function() {
if (this.template.scale && !this.metric) if (this.template.scale && !this.metric) {
this.model = this.view * this.template.scale; this.model = this.view * this.template.scale;
else this.model = this.view; } else {
this.model = this.view;
}
} }
}, },
ready: function() {
ready: function () {this.view = this._view}, this.view = this._view;
},
methods: { methods: {
change: function () {this.$dispatch('input-changed')} change: function() {
this.$dispatch("input-changed");
} }
} }
};

View File

@@ -1,39 +1,12 @@
/******************************************************************************\ "use strict";
This file is part of the Buildbotics firmware. const api = require("./api");
const modbus = require("./modbus.js");
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';
const api = require('./api');
const modbus = require('./modbus.js');
const merge = require("lodash.merge"); const merge = require("lodash.merge");
module.exports = { module.exports = {
template: '#tool-view-template', template: "#tool-view-template",
props: ['config', 'template', 'state'], props: [ "config", "template", "state" ],
data: function() { data: function() {
return { return {
@@ -42,88 +15,94 @@ module.exports = {
toolList: [ toolList: [
{ {
id: "disabled", id: "disabled",
name: "Disabled" name: "Disabled",
}, },
{ {
id: "router", id: "router",
type: "PWM Spindle", type: "PWM Spindle",
name: "Router (Makita, etc)" name: "Router (Makita, etc)",
}, },
{ {
id: "laser", id: "laser",
type: "PWM Spindle", type: "PWM Spindle",
name: "Laser (J Tech, etc)" name: "Laser (J Tech, etc)",
}, },
{ {
id: "pwm", id: "pwm",
name: "PWM Spindle" name: "PWM Spindle",
}, },
{ {
id: "unsupported-separator", id: "unsupported-separator",
name: "Unsupported Tools", name: "Unsupported Tools",
disabled: true, disabled: true,
unsupported: true unsupported: true,
}, },
{ {
id: "huanyang-vfd", id: "huanyang-vfd",
name: "Huanyang VFD", name: "Huanyang VFD",
unsupported: true unsupported: true,
}, },
{ {
id: "custom-modbus-vfd", id: "custom-modbus-vfd",
name: "Custom Modbus VFD", name: "Custom Modbus VFD",
unsupported: true unsupported: true,
}, },
{ {
id: "ac-tech-vfd", id: "ac-tech-vfd",
name: "AC-Tech VFD", name: "AC-Tech VFD",
unsupported: true unsupported: true,
}, },
{ {
id: "nowforever-vfd", id: "nowforever-vfd",
name: "Nowforever VFD", name: "Nowforever VFD",
unsupported: true unsupported: true,
}, },
{ {
id: "delta-vfd", id: "delta-vfd",
name: "Delta VFD015M21A (Beta)", name: "Delta VFD015M21A (Beta)",
unsupported: true unsupported: true,
}, },
{ {
id: "yl600-vfd", id: "yl600-vfd",
name: "YL600, YL620, YL620-A VFD (Beta)", name: "YL600, YL620, YL620-A VFD (Beta)",
unsupported: true unsupported: true,
}, },
{ {
id: "fr-d700-vfd", id: "fr-d700-vfd",
name: "FR-D700 (Beta)", name: "FR-D700 (Beta)",
unsupported: true unsupported: true,
}, },
{ {
id: "sunfar-e300-vfd", id: "sunfar-e300-vfd",
name: "Sunfar E300 (Beta)", name: "Sunfar E300 (Beta)",
unsupported: true unsupported: true,
}, },
{ {
id: "omron-mx2-vfd", id: "omron-mx2-vfd",
name: "OMRON MX2", name: "OMRON MX2",
unsupported: true unsupported: true,
} },
] {
} id: "EM60",
name: "EM60",
},
],
};
}, },
components: { components: {
'modbus-reg': require('./modbus-reg.js') "modbus-reg": require("./modbus-reg.js")
}, },
watch: { watch: {
'state.mr': function () { this.value = this.state.mr } "state.mr": function() {
this.value = this.state.mr;
}
}, },
events: { events: {
'input-changed': function () { "input-changed": function() {
this.$dispatch('config-changed'); this.$dispatch("config-changed");
return false; return false;
}, },
@@ -135,19 +114,19 @@ module.exports = {
computed: { computed: {
regs_tmpl: function() { regs_tmpl: function() {
return this.template['modbus-spindle'].regs; return this.template["modbus-spindle"].regs;
}, },
tool_type: function() { tool_type: function() {
return this.config.tool['tool-type'].toUpperCase(); return this.config.tool["tool-type"].toUpperCase();
}, },
selected_tool: function() { selected_tool: function() {
return this.config.tool['selected-tool']; return this.config.tool["selected-tool"];
}, },
is_pwm_spindle: function() { is_pwm_spindle: function() {
return this.selected_tool == 'pwm'; return this.selected_tool == "pwm";
}, },
is_modbus: function() { is_modbus: function() {
@@ -170,13 +149,13 @@ module.exports = {
methods: { methods: {
change_selected_tool: function() { change_selected_tool: function() {
const selectedToolSettings = this.config['selected-tool-settings'] || {}; const selectedToolSettings = this.config["selected-tool-settings"] || {};
const settings = selectedToolSettings[this.selected_tool] || {}; const settings = selectedToolSettings[this.selected_tool] || {};
this.config.tool = merge({}, this.config.tool, settings['tool']); this.config.tool = merge({}, this.config.tool, settings["tool"]);
this.config['pwm-spindle'] = merge({}, this.config['pwm-spindle'], settings['pwm-spindle']); this.config["pwm-spindle"] = merge({}, this.config["pwm-spindle"], settings["pwm-spindle"]);
this.config['modbus-spindle'] = merge({}, this.config['modbus-spindle'], settings['modbus-spindle']); this.config["modbus-spindle"] = merge({}, this.config["modbus-spindle"], settings["modbus-spindle"]);
const tool = this.toolList.find(tool => tool.id == this.config.tool['selected-tool']); const tool = this.toolList.find(tool => tool.id == this.config.tool["selected-tool"]);
this.config.tool["tool-type"] = tool.type || tool.name; this.config.tool["tool-type"] = tool.type || tool.name;
this.$dispatch("config-changed"); this.$dispatch("config-changed");
@@ -207,71 +186,71 @@ module.exports = {
}, },
get_reg_type: function(reg) { get_reg_type: function(reg) {
return this.regs_tmpl.template['reg-type'].values[this.state[reg + 'vt']]; return this.regs_tmpl.template["reg-type"].values[this.state[`${reg}vt`]];
}, },
get_reg_addr: function(reg) { get_reg_addr: function(reg) {
return this.state[reg + 'va']; return this.state[`${reg}va`];
}, },
get_reg_value: function(reg) { get_reg_value: function(reg) {
return this.state[reg + 'vv']; return this.state[`${reg}vv`];
}, },
get_reg_fails: function(reg) { get_reg_fails: function(reg) {
const fails = this.state[reg + 'vr'] const fails = this.state[`${reg}vr`];
return fails == 255 ? 'Max' : fails; return fails == 255 ? "Max" : fails;
}, },
show_modbus_field: function(key) { show_modbus_field: function(key) {
return key != 'regs' && return key != "regs" && (key != "multi-write" || this.tool_type == "CUSTOM MODBUS VFD");
(key != 'multi-write' || this.tool_type == 'CUSTOM MODBUS VFD');
}, },
read: function(e) { read: function(e) {
e.preventDefault(); e.preventDefault();
api.put('modbus/read', { address: this.address }); api.put("modbus/read", { address: this.address });
}, },
write: function(e) { write: function(e) {
e.preventDefault(); e.preventDefault();
api.put('modbus/write', { address: this.address, value: this.value }); api.put("modbus/write", { address: this.address, value: this.value });
}, },
customize: function(e) { customize: function(e) {
e.preventDefault(); e.preventDefault();
this.config.tool['tool-type'] = 'Custom Modbus VFD'; this.config.tool["tool-type"] = "Custom Modbus VFD";
const regs = this.config['modbus-spindle'].regs; const regs = this.config["modbus-spindle"].regs;
for (let i = 0; i < regs.length; i++) { for (let i = 0; i < regs.length; i++) {
const reg = this.regs_tmpl.index[i]; const reg = this.regs_tmpl.index[i];
regs[i]['reg-type'] = this.get_reg_type(reg); regs[i]["reg-type"] = this.get_reg_type(reg);
regs[i]['reg-addr'] = this.get_reg_addr(reg); regs[i]["reg-addr"] = this.get_reg_addr(reg);
regs[i]['reg-value'] = this.get_reg_value(reg); regs[i]["reg-value"] = this.get_reg_value(reg);
} }
this.$dispatch('config-changed'); this.$dispatch("config-changed");
}, },
clear: function(e) { clear: function(e) {
e.preventDefault(); e.preventDefault();
this.config.tool['tool-type'] = 'Custom Modbus VFD'; this.config.tool["tool-type"] = "Custom Modbus VFD";
const regs = this.config['modbus-spindle'].regs; const regs = this.config["modbus-spindle"].regs;
for (let i = 0; i < regs.length; i++) { for (let i = 0; i < regs.length; i++) {
regs[i]['reg-type'] = 'disabled'; regs[i]["reg-type"] = "disabled";
regs[i]['reg-addr'] = 0; regs[i]["reg-addr"] = 0;
regs[i]['reg-value'] = 0; regs[i]["reg-value"] = 0;
} }
this.$dispatch('config-changed'); this.$dispatch("config-changed");
}, },
reset_failures: function(e) { reset_failures: function(e) {
e.preventDefault(); e.preventDefault();
const regs = this.config['modbus-spindle'].regs; const regs = this.config["modbus-spindle"].regs;
for (let reg = 0; reg < regs.length; reg++) for (let reg = 0; reg < regs.length; reg++) {
this.$dispatch('send', '\$' + reg + 'vr=0'); this.$dispatch("send", `$${reg}vr=0`);
} }
} }
} }
};

View File

@@ -1,58 +1,47 @@
/******************************************************************************\ "use strict";
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 = { module.exports = {
replace: true, replace: true,
template: '{{text}}<span class="unit">{{metric ? unit : iunit}}</span>', template: '{{text}}<span class="unit">{{metric ? unit : iunit}}</span>',
props: ['value', 'precision', 'unit', 'iunit', 'scale'], props: [ "value", "precision", "unit", "iunit", "scale" ],
computed: { computed: {
metric: function () {return !this.$root.state.imperial}, metric: {
cache: false,
get: function() {
return this.$root.display_units === "METRIC";
}
},
text: function() { text: function() {
var value = this.value; let value = this.value;
if (typeof value == 'undefined') return ''; if (typeof value == "undefined") {
return "";
}
if (!this.metric) value /= this.scale; if (!this.metric) {
value /= this.scale;
}
return (1 * value.toFixed(this.precision)).toLocaleString(); return (1 * value.toFixed(this.precision)).toLocaleString();
} }
}, },
ready: function() { ready: function() {
if (typeof this.precision == 'undefined') this.precision = 0; if (typeof this.precision == "undefined") {
if (typeof this.unit == 'undefined') this.unit = 'mm'; this.precision = 0;
if (typeof this.iunit == 'undefined') this.iunit = 'in'; }
if (typeof this.scale == 'undefined') this.scale = 25.4;
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;
} }
} }
};

9
src/js/utils.js Normal file
View File

@@ -0,0 +1,9 @@
function clickFileInput(formClass) {
const form = document.querySelector(`.${formClass}`);
form.reset();
form.querySelector("input").click();
}
module.exports = {
clickFileInput
};

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
doctype html doctype html
html(lang="en") html(lang="en")
head head
@@ -39,12 +12,17 @@ html(lang="en")
style: include ../static/css/font-awesome.min.css style: include ../static/css/font-awesome.min.css
style: include ../static/css/Audiowide.css style: include ../static/css/Audiowide.css
style: include ../static/css/clusterize.css style: include ../static/css/clusterize.css
style: include ../svelte-components/node_modules/svelte-material-ui/bare.css
style: include ../../build/http/svelte-components/smui.css
style: include ../../build/http/svelte-components/style.css
style: include:stylus ../stylus/style.styl style: include:stylus ../stylus/style.styl
body(v-cloak) body(v-cloak)
#svelte-dialog-host
#overlay(v-if="status != 'connected'") #overlay(v-if="status != 'connected'")
span {{status}} span {{status}}
#layout #layout
a#menuLink.menu-link(href="#menu"): span a#menuLink.menu-link(href="#menu"): span
@@ -87,37 +65,27 @@ html(lang="en")
li.pure-menu-heading li.pure-menu-heading
a.pure-menu-link(href="#help") Help a.pure-menu-link(href="#help") Help
button.pure-button.pure-button-primary(@click="confirmShutdown = true", style="width: 100%") button.pure-button.pure-button-primary(@click="showShutdownDialog", style="width: 100%")
.fa.fa-power-off .fa.fa-power-off
message(:show.sync="confirmShutdown")
h3(slot="header") Confirm shutdown?
p(slot="body") Please wait for black screen before switching off power.
div(slot="footer")
button.pure-button(@click="confirmShutdown = false") Cancel
button.pure-button.button-success(@click="shutdown") Shutdown
button.pure-button.button-success(@click="reboot") Restart
#main #main
.header .nav-header
.header-content .brand
.banner
img(src="/images/onefinity_logo.png") img(src="/images/onefinity_logo.png")
.title .version
div Version: v{{config.full_version}}
div IP Address: {{config.ip}}
div WiFi: {{config.wifiName}}
a.upgrade-link(v-if="show_upgrade()", href="#admin-general")
| Upgrade to v{{latestVersion}}
.fa.fa-exclamation-circle.upgrade-attention(v-if="show_upgrade()")
.pi-temp-warning
.fa.fa-thermometer-full(class="error", .fa.fa-thermometer-full(class="error",
v-if="80 <= state.rpi_temp", v-if="80 <= state.rpi_temp",
title="Raspberry Pi temperature too high.") title="Raspberry Pi temperature too high.")
.subtitle
| CNC Controller #[b {{state.demo ? 'Demo ' : ''}}]
| v{{config.full_version}}
a.upgrade-version(v-if="show_upgrade()", href="#admin-general")
| Upgrade to v{{latestVersion}}
.fa.fa-check(v-if="!show_upgrade() && latestVersion",
title="Firmware up to date" style="font-size: inherit")
.p {{get_ip_address()}} {{get_ssid()}}
.estop(:class="{active: state.es}") .whitespace
estop(@click="estop")
.video(title="Plug camera into USB.\n" + .video(title="Plug camera into USB.\n" +
"Left click to toggle video size.\n" + "Left click to toggle video size.\n" +
@@ -129,7 +97,8 @@ html(lang="en")
.box .box
img(src="/api/video") img(src="/api/video")
.clear .estop(:class="{active: state.es}")
estop(@click="estop")
.content(class="{{currentView}}-view") .content(class="{{currentView}}-view")
component(:is="currentView + '-view'", :index="index", component(:is="currentView + '-view'", :index="index",
@@ -205,10 +174,10 @@ html(lang="en")
#templates: include ../../build/templates.pug #templates: include ../../build/templates.pug
iframe#download-target(style="display:none") iframe#download-target(style="display:none")
script: include ../static/js/jquery-1.11.3.min.js
script: include ../static/js/vue.js script: include ../static/js/vue.js
script: include ../static/js/sockjs.min.js script: include ../static/js/sockjs.min.js
script: include ../static/js/clusterize.min.js script: include ../static/js/clusterize.min.js
script: include ../static/js/three.min.js script: include ../static/js/three.min.js
script: include:browserify ../js/main.js script: include:browserify ../js/main.js
script: include ../../build/http/svelte-components/index.js
script: include ../static/js/ui.js script: include ../static/js/ui.js

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#admin-general-view-template(type="text/x-template") script#admin-general-view-template(type="text/x-template")
#admin-general #admin-general
h2 Firmware h2 Firmware
@@ -45,9 +18,6 @@ script#admin-general-view-template(type="text/x-template")
label.pure-button.pure-button-primary(@click="restore_config") Restore label.pure-button.pure-button-primary(@click="restore_config") Restore
form.restore-config.file-upload form.restore-config.file-upload
input(type="file", accept=".json", @change="restore") input(type="file", accept=".json", @change="restore")
message(:show.sync="configRestored")
h3(slot="header") Success
p(slot="body") Configuration restored.
button.pure-button.pure-button-primary(@click="confirmReset = true") Reset button.pure-button.pure-button-primary(@click="confirmReset = true") Reset
message(:show.sync="confirmReset") message(:show.sync="confirmReset")
@@ -68,11 +38,22 @@ script#admin-general-view-template(type="text/x-template")
label(for="tab4", title="Journeyman X-50") Journeyman X-50 label(for="tab4", title="Journeyman X-50") Journeyman X-50
div(slot="footer") div(slot="footer")
button.pure-button(@click="confirmReset = false") Cancel button.pure-button(@click="confirmReset = false") Cancel
button.pure-button.pure-button-primary(@click="reset") Reset button.pure-button.pure-button-primary(@click="next") next
message(:show.sync="configReset") message(:show.sync="z_slider")
h3(slot="header") Success h3(slot="header") Pick your Z Slider Type
p(slot="body") Configuration reset. p(slot="body") Non-network configuration changes will be lost.
p(slot="body") Select defaults to restore:
p.reset-variants(slot="body")
input#tab1(type="radio", name="z_slider_variant" @click="z_slider_variant = 'Z-16 Original'")
label(for="tab1", title="Z-16 Original") Z-16 Original
input#tab2(type="radio", name="z_slider_variant" @click="z_slider_variant = 'Z-20 Heavy Duty'")
label(for="tab2", title="Z-20 Heavy Duty") Z-20 Heavy Duty
div(slot="footer")
button.pure-button(@click="z_slider = false") Cancel
button.pure-button.pure-button-primary(@click="set_z_slider") set z slider
h2 Debugging h2 Debugging
a(href="/api/log", target="_blank") a(href="/api/log", target="_blank")

View File

@@ -1,130 +1,2 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#admin-network-view-template(type="text/x-template") script#admin-network-view-template(type="text/x-template")
#admin-network #admin-network
h2 Hostname
.pure-form.pure-form-aligned
.pure-control-group
label(for="hostname") Hostname
input(name="hostname", v-model="hostname", @keyup.enter="set_hostname")
button.pure-button.pure-button-primary(@click="set_hostname") Set
message(:show.sync="hostnameSet")
h3(slot="header") Hostname Set
div(slot="body")
p Hostname was successfuly set to #[strong {{hostname}}].
p Rebooting to apply changes.
p Redirecting to new hostname in {{redirectTimeout}} seconds.
div(slot="footer")
h2 Remote SSH User
.pure-form.pure-form-aligned
.pure-control-group
label(for="username") Username
input(name="username", v-model="username", @keyup.enter="set_username")
button.pure-button.pure-button-primary(@click="set_username") Set
.pure-form.pure-form-aligned
.pure-control-group
label(for="current") Current Password
input(name="current", v-model="current", type="password")
.pure-control-group
label(for="pass1") New Password
input(name="pass1", v-model="password", type="password")
.pure-control-group
label(for="pass2") New Password
input(name="pass2", v-model="password2", type="password")
button.pure-button.pure-button-primary(@click="set_password") Set
message(:show.sync="passwordSet")
h3(slot="header") Password Set
p(slot="body")
message(:show.sync="usernameSet")
h3(slot="header") Username Set
p(slot="body")
h2 Wifi Setup
.pure-form.pure-form-aligned
.pure-control-group
label(for="wifi_mode") Mode
select(name="wifi_mode", v-model="wifi_mode",
title="Select client or access point mode")
option(value="disabled") Disabled
option(value="client") Client
option(value="ap") Access Point
button.pure-button.pure-button-primary(@click="wifiConfirm = true",
v-if="wifi_mode == 'disabled'") Set
.pure-control-group(v-if="wifi_mode == 'ap'")
label(for="wifi_ch") Channel
select(name="wifi_ch", v-model="wifi_ch")
each ch in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
option(value=ch)= ch
.pure-control-group(v-if="wifi_mode != 'disabled'")
label(for="ssid") Network (SSID)
input(name="ssid", v-model="wifi_ssid")
.pure-control-group(v-if="wifi_mode != 'disabled'")
label(for="wifi_pass") Password
input(name="wifi_pass", v-model="wifi_pass", type="password")
button.pure-button.pure-button-primary(@click="wifiConfirm = true") Set
p(v-if="wifi_mode != 'disabled'").
WARNING: WiFi may be unreliable in an electrically noisy environment
such as a machine shop.
message.wifi-confirm(:show.sync="wifiConfirm")
h3(slot="header") Configure Wifi and reboot?
div(slot="body")
p
| After configuring the Wifi settings the controller will
| automatically reboot.
table
tr
th Mode
td &nbsp;{{wifi_mode}}
tr(v-if="wifi_mode == 'ap'")
th Channel
td &nbsp;{{wifi_ch}}
tr(v-if="wifi_mode != 'disabled'")
th SSID
td &nbsp;{{wifi_ssid}}
tr(v-if="wifi_mode != 'disabled'")
th Auth
td &nbsp;{{wifi_pass ? 'WPA2' : 'Open'}}
div(slot="footer")
button.pure-button(@click="wifiConfirm = false") Cancel
button.pure-button.button-success(@click="config_wifi") OK
message(:show.sync="rebooting")
h3(slot="header") Rebooting
p(slot="body") Please wait...
div(slot="footer")

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#axis-control-template(type="text/x-template") script#axis-control-template(type="text/x-template")
svg(xmlns="http://www.w3.org/2000/svg", svg(xmlns="http://www.w3.org/2000/svg",
xmlns:xlink="http://www.w3.org/1999/xlink", xmlns:xlink="http://www.w3.org/1999/xlink",

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#cheat-sheet-view-template(type="text/x-template") script#cheat-sheet-view-template(type="text/x-template")
// Modified from http://linuxcnc.org/docs/html/gcode.html // Modified from http://linuxcnc.org/docs/html/gcode.html
- var base = 'http://linuxcnc.org/docs/html/gcode'; - var base = 'http://linuxcnc.org/docs/html/gcode';

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#console-template(type="text/x-template") script#console-template(type="text/x-template")
.console .console
.toolbar .toolbar

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#control-view-template(type="text/x-template") script#control-view-template(type="text/x-template")
#control #control
message(:show.sync="showGcodeMessage") message(:show.sync="showGcodeMessage")
@@ -37,109 +10,6 @@ script#control-view-template(type="text/x-template")
div(slot="footer") div(slot="footer")
label Simulating {{(toolpath_progress || 0) | percent}} label Simulating {{(toolpath_progress || 0) | percent}}
message(:show.sync=`ask_home_msg`)
h3(slot="header") Home Machine
div(slot="body")
p Home the machine?
div(slot="footer")
button.pure-button(@click="home()")
| OK
button.pure-button(@click='ask_home_msg = false; ask_home = false')
| Cancel
message(:show.sync=`ask_zero_xy_msg`)
h3(slot="header") XY Origin
div(slot="body")
p Move to XY origin?
div(slot="footer")
button.pure-button(@click="goto_zero(1,1,0,0)")
| Confirm
button.pure-button(@click='ask_zero_xy_msg = false')
| Cancel
message(:show.sync=`ask_zero_z_msg`)
h3(slot="header") Z Origin
div(slot="body")
p Move to Z origin?
div(slot="footer")
button.pure-button(@click="goto_zero(0,0,1,0)")
| Confirm
button.pure-button(@click='ask_zero_z_msg = false')
| Cancel
message(:show.sync=`show_probe_test_modal`)
h3(slot="header") Test probe connection
div(slot="body")
.pure-form
p Attach the probe magnet to the collet.
p Touch the probe block to the bit.
div(slot="footer")
button.pure-button(@click=`show_probe_test_modal = false`)
| Cancel
button.pure-button.button-success(
:disabled=`!state.saw_probe_connected`
@click=`finish_probe_test()`) Continue
message(:show.sync=`show_tool_diameter_modal`)
h3(slot="header") Enter probe tool information
div(slot="body")
.pure-form
.pure-control-group
label="{{metric ? 'Diameter (mm)' : 'Diameter (inches)'}}"
input(v-model="tool_diameter_for_prompt", size="8")
p
div(slot="footer")
button.pure-button(@click=`show_tool_diameter_modal = false`)
| Cancel
button.pure-button.button-success(
@click=`set_tool_diameter`)
| Set
message(:show.sync=`state.show_probe_complete_modal`)
h3(slot="header") Probing complete!
div(slot="body")
.pure-form
p Don't forget to put away the probe!
div(v-if="state.goto_xy_zero_after_probe")
p
| The machine will now move
br
| to the X-Y zero point.
p Watch your hands!
div(slot="footer")
button.pure-button.button-success(@click=`$emit("finalize_probe")`)
| Done
message(:show.sync=`state.show_probe_failed_modal`)
h3(slot="header") Probing failed!
div(slot="body")
.pure-form
p Could not find the probe block during probing!
p Make sure the tip of the bit is about 1/4" (~6mm) above the probe block, and try again.
div(slot="footer")
button.pure-button.button-success(@click=`hide_probe_failed_modal()`)
| OK
table(style="table-layout: fixed; width: 100%;") table(style="table-layout: fixed; width: 100%;")
tr(style="height: fit-content;") tr(style="height: fit-content;")
td(style="white-space: nowrap; width: 410px;", rowspan="2") td(style="white-space: nowrap; width: 410px;", rowspan="2")
@@ -151,55 +21,60 @@ script#control-view-template(type="text/x-template")
col(style="width:100px") col(style="width:100px")
tr tr
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="jog_fn(-1,1,0,0)") button(@click="jog_fn(-1,1,0,0)")
.fa.fa-arrow-right(style="transform: rotate(-135deg);") .fa.fa-arrow-right(style="transform: rotate(-135deg);")
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="jog_fn(0,1,0,0)") Y+ button(@click="jog_fn(0,1,0,0)") Y+
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="jog_fn(1,1,0,0)") button(@click="jog_fn(1,1,0,0)")
.fa.fa-arrow-right(style="transform: rotate(-45deg);") .fa.fa-arrow-right(style="transform: rotate(-45deg);")
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",,@click="jog_fn(0,0,1,0)") Z+ button(,@click="jog_fn(0,0,1,0)") Z+
tr tr
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="jog_fn(-1,0,0,0)") X- button(@click="jog_fn(-1,0,0,0)") X-
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="ask_zero_xy_msg = true") button(@click="showMoveToZeroDialog('xy')")
.fa.fa-bullseye(style="font-size: 172%") .fa.fa-bullseye(style="font-size: 173%")
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="jog_fn(1,0,0,0)") X+ button(@click="jog_fn(1,0,0,0)") X+
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click='ask_zero_z_msg = true') Z0 button(@click="showMoveToZeroDialog('z')") Z0
tr tr
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="jog_fn(-1,-1,0,0)") button(@click="jog_fn(-1,-1,0,0)")
.fa.fa-arrow-right(style="transform: rotate(135deg);") .fa.fa-arrow-right(style="transform: rotate(135deg);")
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="jog_fn(0,-1,0,0)") Y- button(@click="jog_fn(0,-1,0,0)") Y-
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="jog_fn(1,-1,0,0)") button(@click="jog_fn(1,-1,0,0)")
.fa.fa-arrow-right(style="transform: rotate(45deg);") .fa.fa-arrow-right(style="transform: rotate(45deg);")
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(style="height:100px;width:100px",@click="jog_fn(0,0,-1,0)") Z- button(@click="jog_fn(0,0,-1,0)") Z-
tr tr
td(style="height:100px",align="center") td(style="height:100px",align="center")
button#jog_button_fine(style="height:100px;width:100px", @click=`set_jog_incr('fine')`) 0.1 button(:style="getJogIncrStyle('fine')", @click="jog_incr = 'fine'")
span {{jog_incr_amounts[display_units].fine}}#[span.jog-units {{metric ? 'mm' : 'in'}}]
td(style="height:100px",align="center") td(style="height:100px",align="center")
button#jog_button_small(style="height:100px;width:100px", @click=`set_jog_incr('small')`) 1.0 button(:style="getJogIncrStyle('small')", @click="jog_incr = 'small'")
span {{jog_incr_amounts[display_units].small}}#[span.jog-units {{metric ? 'mm' : 'in'}}]
td(style="height:100px",align="center") td(style="height:100px",align="center")
button#jog_button_medium(style="height:100px;width:100px", @click=`set_jog_incr('medium')`) 10 button(:style="getJogIncrStyle('medium')", @click="jog_incr = 'medium'")
span {{jog_incr_amounts[display_units].medium}}#[span.jog-units {{metric ? 'mm' : 'in'}}]
td(style="height:100px",align="center") td(style="height:100px",align="center")
button#jog_button_large(style="height:100px;width:100px", @click=`set_jog_incr('large')`) 100 button(:style="getJogIncrStyle('large')", @click="jog_incr = 'large'")
span {{jog_incr_amounts[display_units].large}}#[span.jog-units {{metric ? 'mm' : 'in'}}]
tr tr
td(style="height:100px", align="center", colspan="2") td(style="height:100px", align="center", colspan="2")
button(:class="state['pw'] ? '' : 'load-on'", button(:class="state['pw'] ? '' : 'load-on'",
style="height:100px;width:200px", style="height:100px;width:200px",
@click=`start_probe_test(prep_and_show_tool_diameter_modal)`) @click="showProbeDialog('xyz')")
| Probe XYZ | Probe XYZ
td(style="height:100px", align="center", colspan="2") td(style="height:100px", align="center", colspan="2")
button(:class="state['pw'] ? '' : 'load-on'", button(:class="state['pw'] ? '' : 'load-on'",
style="height:100px;width:200px", style="height:100px;width:200px",
@click=`start_probe_test(probe_z)`) @click="showProbeDialog('z')")
| Probe Z | Probe Z
td(style="vertical-align: top;") td(style="vertical-align: top;")
@@ -211,8 +86,6 @@ script#control-view-template(type="text/x-template")
th.offset Offset th.offset Offset
th.state State th.state State
th.tstate Toolpath th.tstate Toolpath
//th.tstate Min
//th.tstate Max
th.actions th.actions
button.pure-button(disabled, style="height:60px;width:60px;display:none;") button.pure-button(disabled, style="height:60px;width:60px;display:none;")
@@ -234,21 +107,9 @@ script#control-view-template(type="text/x-template")
td.state td.state
.fa(:class=`'fa-' + ${axis}.icon`) .fa(:class=`'fa-' + ${axis}.icon`)
| {{#{axis}.state}} | {{#{axis}.state}}
td.tstate(:class=`${axis}.tklass`, :title=`${axis}.toolmsg`, @click=`show_toolpath_msg('${axis}')`) td.tstate(:class=`${axis}.tklass`, :title=`${axis}.toolmsg`, @click=`showToolpathMessageDialog('${axis}')`)
.fa(:class=`'fa-' + ${axis}.ticon`) .fa(:class=`'fa-' + ${axis}.ticon`)
| {{#{axis}.tstate}} | {{#{axis}.tstate}}
//td.tstate: unit-value(:value=`${axis}.pathMin`, precision=4)
//td.tstate: unit-value(:value=`${axis}.pathMax`, precision=4)
message(:show.sync=`toolpath_msg['${axis}']`)
h3(slot="header") Tool path info {{'#{axis}' | upper}} axis
div(slot="body")
p {{#{axis}.toolmsg}}
div(slot="footer")
button.pure-button(@click=`toolpath_msg['${axis}'] = false`)
| OK
th.actions th.actions
button.pure-button(:disabled="!can_set_axis", button.pure-button(:disabled="!can_set_axis",
@@ -265,48 +126,6 @@ script#control-view-template(type="text/x-template")
title=`Home {{'${axis}' | upper}} axis.`, style="height:60px;width:60px") title=`Home {{'${axis}' | upper}} axis.`, style="height:60px;width:60px")
.fa.fa-home .fa.fa-home
message(:show.sync=`position_msg['${axis}']`)
h3(slot="header") Set {{'#{axis}' | upper}} axis position
div(slot="body")
.pure-form
.pure-control-group
label Position
input(v-model="axis_position",
@keyup.enter=`set_position('${axis}', axis_position)`)
p
div(slot="footer")
button.pure-button(@click=`position_msg['${axis}'] = false`)
| Cancel
button.pure-button(v-if=`${axis}.homed`,
@click=`unhome('${axis}')`) Unhome
button.pure-button.button-success(
@click=`set_position('${axis}', axis_position)`) Set
message(:show.sync=`manual_home['${axis}']`)
h3(slot="header") Manually home {{'#{axis}' | upper}} axis
div(slot="body")
p Set axis absolute position.
.pure-form
.pure-control-group
label Absolute
input(v-model="axis_position",
@keyup.enter=`set_home('${axis}', axis_position)`)
p
div(slot="footer")
button.pure-button(@click=`manual_home['${axis}'] = false`)
| Cancel
button.pure-button.button-success(
title=`Home {{'${axis}' | upper}} axis.`,
@click=`set_home('${axis}', axis_position)`) Set
tr(style="vertical-align: top;") tr(style="vertical-align: top;")
td td
@@ -323,10 +142,10 @@ script#control-view-template(type="text/x-template")
td.message(:class="{attention: highlight_state}") td.message(:class="{attention: highlight_state}")
| {{message.replace(/^#/, '')}} | {{message.replace(/^#/, '')}}
tr(title="Active machine units") tr
th Units th Display Units
td.mach_units td.units
select(v-model="mach_units", :disabled="!is_idle") select(v-model="display_units")
option(value="METRIC") METRIC option(value="METRIC") METRIC
option(value="IMPERIAL") IMPERIAL option(value="IMPERIAL") IMPERIAL
@@ -368,6 +187,11 @@ script#control-view-template(type="text/x-template")
td td
table.info table.info
tr
th Current Time
td
span {{current_time}}
tr tr
th Remaining th Remaining
td(title="Total run time (days:hours:mins:secs)"). td(title="Total run time (days:hours:mins:secs)").
@@ -376,12 +200,7 @@ script#control-view-template(type="text/x-template")
tr tr
th ETA th ETA
td.eta {{eta}} td.eta {{eta}}
tr
th Line
td
| {{0 <= state.line ? state.line : 0 | number}}
span(v-if="toolpath.lines")
| &nbsp;of {{toolpath.lines | number}}
tr tr
th Progress th Progress
td.progress td.progress
@@ -484,6 +303,9 @@ script#control-view-template(type="text/x-template")
input(v-model="mdi", :disabled="!can_mdi", @keyup.enter="submit_mdi") input(v-model="mdi", :disabled="!can_mdi", @keyup.enter="submit_mdi")
div
em The machine is currently operating in #[strong {{mach_units}}] units. Use G20/G21 to switch units.
.history(:class="{placeholder: !history}") .history(:class="{placeholder: !history}")
span(v-if="!history.length") MDI history displays here. span(v-if="!history.length") MDI history displays here.
ul ul
@@ -497,9 +319,6 @@ script#control-view-template(type="text/x-template")
section#content4.tab-content section#content4.tab-content
indicators(:state="state", :template="template") indicators(:state="state", :template="template")
.override(title="Feed rate override.") .override(title="Feed rate override.")
label Feed label Feed
input(type="range", min="0", max="2", step="0.01", input(type="range", min="0", max="2", step="0.01",

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#estop-template(type="text/x-template") script#estop-template(type="text/x-template")
svg(version="1.1", xmlns:svg="http://www.w3.org/2000/svg", svg(version="1.1", xmlns:svg="http://www.w3.org/2000/svg",
xmlns="http://www.w3.org/2000/svg", xmlns="http://www.w3.org/2000/svg",

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#gcode-viewer-template(type="text/x-template") script#gcode-viewer-template(type="text/x-template")
.gcode .gcode
.clusterize .clusterize

View File

@@ -1,75 +1,2 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#help-view-template(type="text/x-template") script#help-view-template(type="text/x-template")
#help #help
h2 Contact
p
| You can contact us here
|
a(href="https://onefinitycnc.com/support", target="_blank")
| onefinitycnc.com/support
|.
h2 Discussion Forum
p
| Check out our support and discussion forum.
|
a(href="https://forum.onefinitycnc.com", target="_blank")
| forum.onefinitycnc.com
|. Register on the site and post a message. We can't wait to hear from you.
h2 Buildbotics
p
| This controller is based on the Buildbotics CNC Controller.
| We encourage you to check out the
a(href="http://buildbotics.com", target="_blank") Buildbotics Website
|.
h2 CAD/CAM Software
p
a(href="http://wikipedia.com/wiki/Computer-aided_manufacturing",
target="_blank") CAM
|
| software can be used to create GCode
| automatically from
|
a(href="http://wikipedia.com/wiki/Computer-aided_design",
target="_blank") CAD
|
| models. Here are a few CAD/CAM resources:
ul
li: a(href="http://camotics.org/", target="_blank")
| CAMotics - Open-Source CNC Simulator
li: a(href="http://librecad.org/", target="_blank")
| LibreCAD - Open-Source 2D CAD
li: a(href="https://www.freecadweb.org/", target="_blank")
| FreeCAD - Open-Source 3D CAD
li: a(href="http://www.openscad.org/", target="_blank")
| OpenSCAD - Open-Source 3D CAD for programmers
li: a(href="http://wiki.linuxcnc.org/cgi-bin/wiki.pl?Cam",
target="_blank") LinuxCNC CAM resources

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#indicators-template(type="text/x-template") script#indicators-template(type="text/x-template")
.indicators .indicators
table.legend table.legend

View File

@@ -1,29 +1,2 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#io-indicator-template(type="text/x-template") script#io-indicator-template(type="text/x-template")
.fa.io(:class="klass", :title="tooltip") .fa.io(:class="klass", :title="tooltip")

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#io-view-template(type="text/x-template") script#io-view-template(type="text/x-template")
#io #io
h1 I/O Configuration h1 I/O Configuration

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#message-template(type="text/x-template") script#message-template(type="text/x-template")
.modal-mask(v-if="show", :class="class") .modal-mask(v-if="show", :class="class")
.modal-wrapper .modal-wrapper

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#modbus-reg-view-template(type="text/x-template") script#modbus-reg-view-template(type="text/x-template")
tr.modbus-reg tr.modbus-reg
td.reg-index {{index}} td.reg-index {{index}}

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#motor-view-template(type="text/x-template") script#motor-view-template(type="text/x-template")
.motor(:class="{slave: is_slave}") .motor(:class="{slave: is_slave}")
h1 Motor {{index}} Configuration h1 Motor {{index}} Configuration

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#path-viewer-template(type="text/x-template") script#path-viewer-template(type="text/x-template")
.path-viewer(v-show="enabled", :class="{small: small}") .path-viewer(v-show="enabled", :class="{small: small}")
.path-viewer-toolbar .path-viewer-toolbar

View File

@@ -1,85 +1,2 @@
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#settings-view-template(type="text/x-template") script#settings-view-template(type="text/x-template")
#settings #settings
h1 Settings
.pure-form.pure-form-aligned
fieldset
h2 Units
templated-input(name="units", :model.sync="config.settings.units",
:template="template.settings.units")
p
| Note, #[tt units] sets both the machine default units and the
| units used in motor configuration. GCode #[tt program-start],
| set below, may also change the default machine units.
fieldset
h2 Probing safety prompts
templated-input(name="probing-prompts",
:model.sync="config.settings['probing-prompts']",
:template="template.settings['probing-prompts']")
fieldset
h2 Probe Dimensions
templated-input(v-for="templ in template.probe", :name="$key",
:model.sync="config.probe[$key]", :template="templ")
fieldset
h2 GCode
templated-input(v-for="templ in template.gcode", :name="$key",
:model.sync="config.gcode[$key]", :template="templ")
fieldset
h2 Path Accuracy
templated-input(name="max-deviation",
:model.sync="config.settings['max-deviation']",
:template="template.settings['max-deviation']")
p.
Lower #[tt max-deviation] to follow the programmed path more precisely
but at a slower speed.
p.
In order to improve traversal speed, the path planner may merge
consecutive moves or round off sharp corners if doing so would deviate
from the program path by less than #[tt max-deviation].
- var base = '//linuxcnc.org/docs/html/gcode/g-code.html'
p.
GCode commands
#[a(href=base + "#gcode:g61", target="_blank") G61, G61.1] and
#[a(href=base + "#gcode:g64", target="_blank") G64] also affect path
planning accuracy.
h2 Cornering Speed (Advanced)
templated-input(name="junction-accel",
:model.sync="config.settings['junction-accel']",
:template="template.settings['junction-accel']")
p.
Junction acceleration limits the cornering speed the planner will
allow. Increasing this value will allow for faster traversal of
corners but may cause the planner to violate axis jerk limits and
stall the motors. Use with caution.

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#templated-input-template(type="text/x-template") script#templated-input-template(type="text/x-template")
.pure-control-group(class="tmpl-input-{{name}}", :title="title") .pure-control-group(class="tmpl-input-{{name}}", :title="title")
label(:for="name") {{name}} label(:for="name") {{name}}
@@ -38,22 +11,17 @@ script#templated-input-template(type="text/x-template")
input(v-if="template.type == 'float'", v-model.number="view", number, input(v-if="template.type == 'float'", v-model.number="view", number,
:min="template.min", :max="template.max", :step="template.step || 'any'", :min="template.min", :max="template.max", :step="template.step || 'any'",
type="number", :name="name", @change="change") type="number", :name="name", @keyup="change")
input(v-if="template.type == 'int' && !template.values", number, input(v-if="template.type == 'int' && !template.values", number,
v-model.number="view", :min="template.min", :max="template.max", v-model.number="view", :min="template.min", :max="template.max",
type="number", :name="name", @change="change") type="number", :name="name", @keyup="change")
input(v-if="template.type == 'string'", v-model="view", type="text", input(v-if="template.type == 'string'", v-model="view", type="text",
:name="name", @change="change") :name="name", @keyup="change")
textarea(v-if="template.type == 'text'", v-model="view", :name="name", textarea(v-if="template.type == 'text'", v-model="view", :name="name",
@change="change") @keyup="change")
span.range(v-if="template.type == 'percent'")
input(type="range", v-model="view", :name="name", number, min="0",
max="100", step="1", @change="change")
| {{view}}
label.units {{units}} label.units {{units}}

View File

@@ -1,30 +1,3 @@
//-/////////////////////////////////////////////////////////////////////////////
//- //
//- 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> //
//- //
//-/////////////////////////////////////////////////////////////////////////////
script#tool-view-template(type="text/x-template") script#tool-view-template(type="text/x-template")
#tool #tool
h1 Tool Configuration h1 Tool Configuration
@@ -70,7 +43,7 @@ script#tool-view-template(type="text/x-template")
label.units RPM label.units RPM
fieldset.modbus-program( fieldset.modbus-program(
v-if="is_modbus && this.tool_type != 'HUANYANG VFD'") v-if="is_modbus && tool_type != 'HUANYANG VFD' && tool_type != 'EM60'")
h2 Active Modbus Program h2 Active Modbus Program
p(v-if="$root.modified") p(v-if="$root.modified")
| (Click #[tt(class="save") Save] to activate the selected | (Click #[tt(class="save") Save] to activate the selected
@@ -407,3 +380,64 @@ script#tool-view-template(type="text/x-template")
| |
| and spindle type. The VFD must be rebooted after changing | and spindle type. The VFD must be rebooted after changing
| the above settings. | the above settings.
.notes(v-if="tool_type.startsWith('EM60')")
h2 Notes
p Set the following using the VFD's front panel.
table.modbus-regs.fixed-regs
tr
th Address
th Value
th Meaning
th Description
tr
td.reg-addr P0.0.03
td.reg-value 2
td Modbus
td Control mode
tr
td.reg-addr P0.0.04
td.reg-value 9
td Modbus
td Frequency source A
tr
td.reg-addr P0.1.00
td.reg-value 0
td Source A
td Frequency source
tr
td.reg-addr P4.1.00
td.reg-value 3
td 9600 BAUD
td Must match #[tt baud] above
tr
td.reg-addr P4.1.01
td.reg-value 0
td 8N2
td Data format
tr
td.reg-addr P4.1.02
td.reg-value 1
td Bus id
td Must match #[tt bus-id] above
tr
td.reg-addr P4.1.03
td.reg-value 2
td No delay
td Communications response delay
tr
td.reg-addr P4.1.04
td.reg-value 0
td No timeout
td Communications timeout
tr
td.reg-addr P4.1.05
td.reg-value 1
td RTU
td Modbus format
p
| Other settings according to the
|
a(href="https://buildbotics.com/upload/vfd/em60.pdf",
target="_blank") EM60 VFD manual

View File

@@ -43,7 +43,7 @@ class Config(object):
self.values = {} self.values = {}
try: try:
self.version = pkg_resources.require('bbctrl')[0].version self.version = "1.2.1"
# Load config template # Load config template
with open(get_resource('http/config-template.json'), 'r', with open(get_resource('http/config-template.json'), 'r',

View File

@@ -1,52 +1,43 @@
################################################################################
# #
# 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> #
# #
################################################################################
import os import os
import tempfile
import bbctrl import bbctrl
import glob import glob
import html import tornado
from tornado import gen from tornado import gen
from tornado.web import HTTPError from tornado.web import HTTPError
from tornado.escape import url_unescape
def safe_remove(path): def safe_remove(path):
try: try:
os.unlink(path) os.unlink(path)
except OSError: pass except OSError:
pass
@tornado.web.stream_request_body
class FileHandler(bbctrl.APIHandler): class FileHandler(bbctrl.APIHandler):
def prepare(self): pass def prepare(self):
if self.request.method == 'PUT':
self.request.connection.set_max_body_size(2 ** 30)
filename = self.request.path.split('/')[-1]
self.uploadFilename = url_unescape(filename) \
.replace('\\', '/') \
.replace('#', '-') \
.replace('?', '-')
self.uploadFile = tempfile.NamedTemporaryFile("wb")
def data_received(self, data):
if self.request.method == 'PUT':
self.uploadFile.write(data)
def delete_ok(self, filename): def delete_ok(self, filename):
if not filename: if not filename:
# Delete everything # Delete everything
for path in glob.glob(self.get_upload('*')): safe_remove(path) for path in glob.glob(self.get_upload('*')):
safe_remove(path)
self.get_ctrl().preplanner.delete_all_plans() self.get_ctrl().preplanner.delete_all_plans()
self.get_ctrl().state.clear_files() self.get_ctrl().state.clear_files()
@@ -57,26 +48,29 @@ class FileHandler(bbctrl.APIHandler):
self.get_ctrl().preplanner.delete_plans(filename) self.get_ctrl().preplanner.delete_plans(filename)
self.get_ctrl().state.remove_file(filename) self.get_ctrl().state.remove_file(filename)
def put_ok(self, *args): def put_ok(self, *args):
gcode = self.request.files['gcode'][0] if not os.path.exists(self.get_upload()):
filename = os.path.basename(gcode['filename'].replace('\\', '/')) os.mkdir(self.get_upload())
filename = filename.replace('#', '-').replace('?', '-')
if not os.path.exists(self.get_upload()): os.mkdir(self.get_upload()) filename = self.get_upload(self.uploadFilename).encode('utf8')
safe_remove(filename)
os.link(self.uploadFile.name, filename)
with open(self.get_upload(filename).encode('utf8'), 'wb') as f: self.uploadFile.close()
f.write(gcode['body'])
os.sync()
self.get_ctrl().preplanner.invalidate(filename) del (self.uploadFile)
self.get_ctrl().state.add_file(filename)
self.get_log('FileHandler').info('GCode received: ' + filename)
self.get_ctrl().preplanner.invalidate(self.uploadFilename)
self.get_ctrl().state.add_file(self.uploadFilename)
self.get_log('FileHandler').info(
'GCode received: ' + self.uploadFilename)
del (self.uploadFilename)
@gen.coroutine @gen.coroutine
def get(self, filename): def get(self, filename):
if not filename: raise HTTPError(400, 'Missing filename') if not filename:
raise HTTPError(400, 'Missing filename')
filename = os.path.basename(filename) filename = os.path.basename(filename)
try: try:
@@ -84,6 +78,7 @@ class FileHandler(bbctrl.APIHandler):
self.write(f.read()) self.write(f.read())
except Exception: except Exception:
self.get_ctrl().state.select_file('') self.get_ctrl().state.select_file('')
raise HTTPError(400, "Unable to read file - doesn't appear to be GCode.") raise HTTPError(
400, "Unable to read file - doesn't appear to be GCode.")
self.get_ctrl().state.select_file(filename) self.get_ctrl().state.select_file(filename)

View File

@@ -1,30 +1,3 @@
################################################################################
# #
# 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> #
# #
################################################################################
import os import os
import json import json
import tornado import tornado
@@ -34,9 +7,10 @@ import subprocess
import socket import socket
from tornado.web import HTTPError from tornado.web import HTTPError
from tornado import gen from tornado import gen
import re
import bbctrl import bbctrl
from urllib.request import urlopen
import iw_parse
def call_get_output(cmd): def call_get_output(cmd):
@@ -75,7 +49,7 @@ def check_password(password):
class RebootHandler(bbctrl.APIHandler): class RebootHandler(bbctrl.APIHandler):
def put_ok(self): def put_ok(self):
self.get_ctrl().lcd.goodbye('Rebooting...') self.get_ctrl().lcd.goodbye('Rebooting...')
subprocess.Popen('reboot') subprocess.Popen(['reboot'])
class ShutdownHandler(bbctrl.APIHandler): class ShutdownHandler(bbctrl.APIHandler):
def put_ok(self): def put_ok(self):
@@ -153,34 +127,81 @@ class HostnameHandler(bbctrl.APIHandler):
raise HTTPError(400, 'Failed to set hostname') raise HTTPError(400, 'Failed to set hostname')
class WifiHandler(bbctrl.APIHandler): class NetworkData(bbctrl.APIHandler):
def get(self):
data = {'ssid': '', 'channel': 0}
try:
data = json.loads(call_get_output(['config-wifi', '-j']))
except: pass
self.write_json(data)
def get(self):
try:
ipAddresses = subprocess.check_output(
"ip -4 addr | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'", shell=True).decode().split()
ipAddresses.remove("127.0.0.1")
regex = re.compile(r'/255$/')
filtered = [i for i in ipAddresses if not regex.match(i)]
ipAddresses = filtered[0]
except:
ipAddresses = "Not Connected"
try:
wifi = subprocess.check_output(
"sudo iw dev wlan0 info | grep ssid", shell=True).decode().split()
wifi.pop(0)
wifiName = " ".join(wifi)
except:
wifiName = "not connected"
self.write_json({
'ipAddresses': ipAddresses,
'wifi': wifiName
})
class NetworkHandler(bbctrl.APIHandler):
def get(self):
try:
ipAddresses = subprocess.check_output(
"ip -4 addr | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'", shell=True).decode().split()
ipAddresses.remove("127.0.0.1")
regex = re.compile(r'/255$/')
filtered = [i for i in ipAddresses if not regex.match(i)]
ipAddresses = filtered[0]
except:
ipAddresses = "Not Connected"
hostname = socket.gethostname()
try:
wifi = json.loads(call_get_output(['config-wifi', '-j']))
except:
wifi = {'enabled': False}
try:
lines = iw_parse.call_iwlist().decode("utf-8").split("\n")
wifi['networks'] = iw_parse.get_parsed_cells(lines)
except:
wifi['networks'] = []
self.write_json({
'ipAddresses': ipAddresses,
'hostname': hostname,
'wifi': wifi
})
def put(self): def put(self):
if self.get_ctrl().args.demo: if self.get_ctrl().args.demo:
raise HTTPError(400, 'Cannot configure WiFi in demo mode') raise HTTPError(400, 'Cannot configure WiFi in demo mode')
if 'mode' in self.json: if not 'wifi' in self.json:
raise HTTPError(400, 'Payload is missing wifi config information')
wifi = self.json['wifi']
cmd = ['config-wifi', '-r'] cmd = ['config-wifi', '-r']
mode = self.json['mode']
if mode == 'disabled': cmd += ['-d'] if not wifi['enabled']:
elif 'ssid' in self.json: cmd += ['-d']
cmd += ['-s', self.json['ssid']] else:
if 'ssid' in wifi:
cmd += ['-s', wifi['ssid']]
if mode == 'ap': if 'password' in wifi:
cmd += ['-a'] cmd += ['-p', wifi['password']]
if 'channel' in self.json:
cmd += ['-c', self.json['channel']]
if 'pass' in self.json:
cmd += ['-p', self.json['pass']]
if subprocess.call(cmd) == 0: if subprocess.call(cmd) == 0:
self.write_json('ok') self.write_json('ok')
@@ -407,6 +428,103 @@ class JogHandler(bbctrl.APIHandler):
self.get_ctrl().mach.jog(self.json) self.get_ctrl().mach.jog(self.json)
displayRotatePattern = re.compile(r'display_rotate\s*=\s*(\d)')
transformationMatrixPattern = re.compile(
r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(\s+Option\s+\"TransformationMatrix\".*?\n)(.*?EndSection)',
re.DOTALL)
matchIsTouchscreenPattern = re.compile(
r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(.*?EndSection)', re.DOTALL)
class ScreenRotationHandler(bbctrl.APIHandler):
@gen.coroutine
def get(self):
with open("/boot/config.txt", 'rt') as config:
lines = config.readlines()
for line in lines:
if line.startswith('display_rotate'):
self.write_json({
'rotated':
int(displayRotatePattern.search(line).group(1)) != 0
})
return
self.write_json({'rotated': False})
return
@gen.coroutine
def put_ok(self):
rotated = self.json['rotated']
subprocess.Popen([
'/usr/local/bin/edit-boot-config',
'display_rotate={}'.format(2 if rotated else 0)
])
with open("/usr/share/X11/xorg.conf.d/40-libinput.conf",
'rt') as config:
text = config.read()
text = transformationMatrixPattern.sub(r'\1\2\3\5', text)
if rotated:
text = matchIsTouchscreenPattern.sub(
r'\1\2\3\2Option "TransformationMatrix" "-1 0 1 0 -1 1 0 0 1"\1\4',
text)
with open("/usr/share/X11/xorg.conf.d/40-libinput.conf",
'wt') as config:
config.write(text)
subprocess.run('reboot')
class TimeHandler(bbctrl.APIHandler):
def get(self):
timeinfo = call_get_output(['timedatectl'])
timezones = call_get_output(
['timedatectl', 'list-timezones', '--no-pager'])
self.get_log('TimeHandler').info('Time stuff: {}, {}'.format(
timeinfo, timezones))
self.write_json({'timeinfo': timeinfo, 'timezones': timezones})
def put_ok(self):
datetime = self.json['datetime']
timezone = self.json['timezone']
subprocess.Popen(['timedatectl', 'set-time', datetime])
subprocess.Popen(['timedatectl', 'set-timezone', timezone])
class RemoteDiagnosticsHandler(bbctrl.APIHandler):
def get(self):
code = self.get_query_argument("code", "")
command = self.get_query_argument("command", "")
log = self.get_log('RemoteDiagnostics')
if command == "disconnect":
subprocess.Popen(['killall', 'ngrok'])
self.write_json({'message': "Succesfully disconnected"})
if command == "connect":
try:
url = 'https://tinyurl.com/1f-remote?code={}'.format(code)
with urlopen(url) as response:
body = response.read()
os.makedirs("/tmp/ngrok", exist_ok=True)
with open("/tmp/ngrok/1f-ngrok.sh", 'wb') as f:
f.write(body)
subprocess.Popen(['/bin/bash', "/tmp/ngrok/1f-ngrok.sh"])
self.write_json({'success': True})
except Exception as e:
log.info("Failed: {}".format(str(e)))
self.write_json({
'success': False,
'code': e.code or None,
'message': e.reason or "Unknown"
})
# Base class for Web Socket connections # Base class for Web Socket connections
class ClientConnection(object): class ClientConnection(object):
def __init__(self, app): def __init__(self, app):
@@ -517,7 +635,8 @@ class Web(tornado.web.Application):
(r'/api/reboot', RebootHandler), (r'/api/reboot', RebootHandler),
(r'/api/shutdown', ShutdownHandler), (r'/api/shutdown', ShutdownHandler),
(r'/api/hostname', HostnameHandler), (r'/api/hostname', HostnameHandler),
(r'/api/wifi', WifiHandler), (r'/api/wifi', NetworkData),
(r'/api/network', NetworkHandler),
(r'/api/remote/username', UsernameHandler), (r'/api/remote/username', UsernameHandler),
(r'/api/remote/password', PasswordHandler), (r'/api/remote/password', PasswordHandler),
(r'/api/config/load', ConfigLoadHandler), (r'/api/config/load', ConfigLoadHandler),
@@ -544,6 +663,9 @@ class Web(tornado.web.Application):
(r'/api/modbus/write', ModbusWriteHandler), (r'/api/modbus/write', ModbusWriteHandler),
(r'/api/jog', JogHandler), (r'/api/jog', JogHandler),
(r'/api/video', bbctrl.VideoHandler), (r'/api/video', bbctrl.VideoHandler),
(r'/api/screen-rotation', ScreenRotationHandler),
(r'/api/time', TimeHandler),
(r'/api/remote-diagnostics', RemoteDiagnosticsHandler),
(r'/(.*)', StaticFileHandler, (r'/(.*)', StaticFileHandler,
{'path': bbctrl.get_resource('http/'), {'path': bbctrl.get_resource('http/'),
'default_filename': 'index.html'}), 'default_filename': 'index.html'}),

View File

@@ -186,10 +186,10 @@ class Plan(object):
def _run(self): def _run(self):
start = time.clock() start = time.time()
line = 0 line = 0
maxLine = 0 maxLine = 0
maxLineTime = time.clock() maxLineTime = time.time()
position = {axis: 0 for axis in 'xyz'} position = {axis: 0 for axis in 'xyz'}
rapid = False rapid = False
@@ -242,7 +242,7 @@ class Plan(object):
line = cmd['value'] line = cmd['value']
if maxLine < line: if maxLine < line:
maxLine = line maxLine = line
maxLineTime = time.clock() maxLineTime = time.time()
elif cmd['name'] == 'speed': elif cmd['name'] == 'speed':
s = cmd['value'] s = cmd['value']
@@ -250,11 +250,11 @@ class Plan(object):
elif cmd['type'] == 'dwell': self.time += cmd['seconds'] elif cmd['type'] == 'dwell': self.time += cmd['seconds']
if args.max_time < time.clock() - start: if args.max_time < time.time() - start:
raise Exception('Max planning time (%d sec) exceeded.' % raise Exception('Max planning time (%d sec) exceeded.' %
args.max_time) args.max_time)
if args.max_loop < time.clock() - maxLineTime: if args.max_loop < time.time() - maxLineTime:
raise Exception('Max loop time (%d sec) exceeded.' % raise Exception('Max loop time (%d sec) exceeded.' %
args.max_loop) args.max_loop)

80
src/py/iw_parse/README.md Normal file
View File

@@ -0,0 +1,80 @@
iw_parse
========
Parse the output of iwlist scan to get the name, address, quality, channel, and encryption type of all networks broadcasting within your Wireless NIC's reach.
Dependencies
------------
* [pip](http://www.pip-installer.org/en/latest/installing.html "pip installation guide") - If you don't have pip installed, followed the link.
Installation
------------
```bash
pip install iw_parse
```
Usage
-----
```bash
iwlist <INTERFACE_NAME> scan | iw_parse
```
Replace `<INTERFACE_NAME>` with the system name for your wireless NIC. It is usually something like `wlan0`. The command `iwconfig` will list all of your network interfaces.
Example:
```bash
iwlist wlan0 scan | iw_parse
```
The result should look something like:
```
Name Address Quality Channel Encryption
wireless1 20:AA:4B:34:2C:F5 100 % 11 WEP
wireless2 00:26:F2:1E:FC:03 84 % 1 WPA v.1
wireless3 00:1D:D3:6A:3C:60 66 % 6 WEP
wireless4 20:10:7A:E5:02:98 64 % 1 WEP
wireless5 CC:A4:62:B7:D2:B0 54 % 8 WPA v.1
wireless6 30:46:9A:53:3C:76 47 % 11 WPA v.1
wireless7 A0:21:B7:5F:84:B0 44 % 11 WEP
wireless8 04:A1:51:18:E8:E0 41 % 6 WPA v.1
```
Example from Python shell:
```python
>>> import iw_parse
>>> networks = iw_parse.get_interfaces(interface='wlan0')
>>> print networks
[{'Address': 'F8:1E:DF:F9:B0:0B',
'Channel': '3',
'Encryption': 'WEP',
'Name': 'Francis',
'Bit Rates': '144 Mb/s',
'Signal Level': '42',
'Name': 'Francis',
'Quality': '100'},
{'Address': '86:1B:5E:33:17:D4',
'Channel': '6',
'Encryption': 'Open',
'Bit Rates': '54 Mb/s',
'Signal Level': '72',
'Name': 'optimumwifi',
'Quality': '100'},
...
```
Acknowledgements
----------------
* The vast majority of iw_parse was written by Hugo Chargois.
License
-------
iw_parse is free--as in BSD. Hack your heart out, hackers.

View File

@@ -0,0 +1 @@
from .iw_parse import *

25
src/py/iw_parse/iw_parse Normal file
View File

@@ -0,0 +1,25 @@
#! /usr/bin/env python
import sys
from iw_parse import get_parsed_cells, print_cells
def main():
""" Pretty prints the output of iwlist scan into a table. """
parsed_cells = get_parsed_cells(sys.stdin)
# You can choose which columns to display here, and most importantly
# in what order. Of course, they must exist as keys in the dict rules.
columns = [
"Name",
"Address",
"Quality",
"Channel",
"Signal Level",
"Encryption"
]
print_cells(parsed_cells, columns)
if __name__ == "__main__":
main()

337
src/py/iw_parse/iw_parse.py Normal file
View File

@@ -0,0 +1,337 @@
#! /usr/bin/env python
# Hugo Chargois - 17 jan. 2010 - v.0.1
# Parses the output of iwlist scan into a table
# You can add or change the functions to parse the properties
# of each AP (cell) below. They take one argument, the bunch of text
# describing one cell in iwlist scan and return a property of that cell.
import re
import subprocess
VERSION_RGX = re.compile("version\s+\d+", re.IGNORECASE)
def get_name(cell):
""" Gets the name / essid of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The name / essid of the network.
"""
essid = matching_line(cell, "ESSID:")
if not essid:
return ""
return essid[1:-1]
def get_quality(cell):
""" Gets the quality of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The quality of the network.
"""
quality = matching_line(cell, "Quality=")
if quality is None:
return ""
quality = quality.split()[0].split("/")
quality = matching_line(cell, "Quality=").split()[0].split("/")
return str(int(round(float(quality[0]) / float(quality[1]) * 100)))
def get_signal_level(cell):
""" Gets the signal level of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The signal level of the network.
"""
signal = matching_line(cell, "Signal level=")
if signal is None:
return ""
signal = signal.split("=")[1].split("/")
if len(signal) == 2:
return str(int(round(float(signal[0]) / float(signal[1]) * 100)))
elif len(signal) == 1:
return signal[0].split(' ')[0]
else:
return ""
def get_noise_level(cell):
""" Gets the noise level of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The noise level of the network.
"""
noise = matching_line(cell, "Noise level=")
if noise is None:
return ""
noise = noise.split("=")[1]
return noise.split(' ')[0]
def get_channel(cell):
""" Gets the channel of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The channel of the network.
"""
channel = matching_line(cell, "Channel:")
if channel:
return channel
frequency = matching_line(cell, "Frequency:")
channel = re.sub(r".*\(Channel\s(\d{1,3})\).*", r"\1", frequency)
return channel
def get_frequency(cell):
""" Gets the frequency of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The frequency of the network.
"""
frequency = matching_line(cell, "Frequency:")
if frequency is None:
return ""
return frequency.split()[0]
def get_encryption(cell, emit_version=False):
""" Gets the encryption type of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The encryption type of the network.
"""
enc = ""
if matching_line(cell, "Encryption key:") == "off":
enc = "Open"
else:
for line in cell:
matching = match(line,"IE:")
if matching == None:
continue
wpa = match(matching,"WPA")
if wpa == None:
continue
version_matches = VERSION_RGX.search(wpa)
if len(version_matches.regs) == 1:
version = version_matches \
.group(0) \
.lower() \
.replace("version", "") \
.strip()
wpa = wpa.replace(version_matches.group(0), "").strip()
if wpa == "":
wpa = "WPA"
if emit_version:
enc = "{0} v.{1}".format(wpa, version)
else:
enc = wpa
if wpa == "WPA2":
return enc
else:
enc = wpa
if enc == "":
enc = "WEP"
return enc
def get_mode(cell):
""" Gets the mode of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The IEEE 802.11 mode of the network.
"""
mode = matching_line(cell, "Extra:ieee_mode=")
if mode is None:
return ""
return mode
def get_address(cell):
""" Gets the address of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The address of the network.
"""
return matching_line(cell, "Address: ")
def get_bit_rates(cell):
""" Gets the bit rate of a network / cell.
@param string cell
A network / cell from iwlist scan.
@return string
The bit rate of the network.
"""
return matching_line(cell, "Bit Rates:")
# Here you can choose the way of sorting the table. sortby should be a key of
# the dictionary rules.
def sort_cells(cells):
sortby = "Quality"
reverse = True
cells.sort(key=lambda el:el[sortby], reverse=reverse)
# Below here goes the boring stuff. You shouldn't have to edit anything below
# this point
def matching_line(lines, keyword):
""" Returns the first matching line in a list of lines.
@see match()
"""
for line in lines:
matching = match(line,keyword)
if matching != None:
return matching
return None
def match(line, keyword):
""" If the first part of line (modulo blanks) matches keyword,
returns the end of that line. Otherwise checks if keyword is
anywhere in the line and returns that section, else returns None"""
line = line.lstrip()
length = len(keyword)
if line[:length] == keyword:
return line[length:]
else:
if keyword in line:
return line[line.index(keyword):]
else:
return None
def parse_cell(cell, rules):
""" Applies the rules to the bunch of text describing a cell.
@param string cell
A network / cell from iwlist scan.
@param dictionary rules
A dictionary of parse rules.
@return dictionary
parsed networks. """
parsed_cell = {}
for key in rules:
rule = rules[key]
parsed_cell.update({key: rule(cell)})
return parsed_cell
def print_table(table):
# Functional black magic.
widths = list(map(max, map(lambda l: map(len, l), zip(*table))))
justified_table = []
for line in table:
justified_line = []
for i, el in enumerate(line):
justified_line.append(el.ljust(widths[i] + 2))
justified_table.append(justified_line)
for line in justified_table:
print("\t".join(line))
def print_cells(cells, columns):
table = [columns]
for cell in cells:
cell_properties = []
for column in columns:
if column == 'Quality':
# make print nicer
cell[column] = cell[column].rjust(3) + " %"
cell_properties.append(cell[column])
table.append(cell_properties)
print_table(table)
def get_parsed_cells(iw_data, rules=None):
""" Parses iwlist output into a list of networks.
@param list iw_data
Output from iwlist scan.
A list of strings.
@return list
properties: Name, Address, Quality, Channel, Frequency, Encryption, Signal Level, Noise Level, Bit Rates, Mode.
"""
# Here's a dictionary of rules that will be applied to the description
# of each cell. The key will be the name of the column in the table.
# The value is a function defined above.
rules = rules or {
"Name": get_name,
"Quality": get_quality,
"Channel": get_channel,
"Frequency": get_frequency,
"Encryption": get_encryption,
"Address": get_address,
"Signal Level": get_signal_level,
"Noise Level": get_noise_level,
"Bit Rates": get_bit_rates,
"Mode": get_mode,
}
cells = [[]]
parsed_cells = []
for line in iw_data:
cell_line = match(line, "Cell ")
if cell_line != None:
cells.append([])
line = cell_line[-27:]
cells[-1].append(line.rstrip())
cells = cells[1:]
for cell in cells:
parsed_cells.append(parse_cell(cell, rules))
sort_cells(parsed_cells)
return parsed_cells
def call_iwlist(interface='wlan0'):
""" Get iwlist output via subprocess
@param string interface
interface to scan
default is wlan0
@return string
properties: iwlist output
"""
return subprocess.check_output(['iwlist', interface, 'scanning'])
def get_interfaces(interface="wlan0"):
""" Get parsed iwlist output
@param string interface
interface to scan
default is wlan0
@param list columns
default data attributes to return
@return dict
properties: dictionary of iwlist attributes
"""
return get_parsed_cells(call_iwlist(interface).split('\n'))

View File

@@ -0,0 +1,10 @@
Copyright (c) 2013, Cuzzo Yahn
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -6,8 +6,7 @@
"default": "METRIC" "default": "METRIC"
}, },
"max-deviation": { "max-deviation": {
"help": "help": "Default allowed deviation from programmed path. Also see G64 & G61.",
"Default allowed deviation from programmed path. Also see G64 & G61.",
"type": "float", "type": "float",
"min": 0.05, "min": 0.05,
"max": 100, "max": 100,
@@ -15,8 +14,7 @@
"default": 0.1 "default": 0.1
}, },
"junction-accel": { "junction-accel": {
"help": "help": "Higher values will increasing cornering speed but may cause stalls.",
"Higher values will increasing cornering speed but may cause stalls.",
"type": "float", "type": "float",
"min": 10000, "min": 10000,
"max": 100000000, "max": 100000000,
@@ -168,7 +166,13 @@
"homing": { "homing": {
"homing-mode": { "homing-mode": {
"type": "enum", "type": "enum",
"values": ["manual", "switch-min", "switch-max","stall-min","stall-max"], "values": [
"manual",
"switch-min",
"switch-max",
"stall-min",
"stall-max"
],
"default": "manual", "default": "manual",
"code": "ho" "code": "ho"
}, },
@@ -266,7 +270,8 @@
"YL600, YL620, YL620-A VFD (Beta)", "YL600, YL620, YL620-A VFD (Beta)",
"FR-D700 (Beta)", "FR-D700 (Beta)",
"Sunfar E300 (Beta)", "Sunfar E300 (Beta)",
"OMRON MX2" "OMRON MX2",
"EM60"
], ],
"default": "Disabled", "default": "Disabled",
"code": "st" "code": "st"
@@ -292,16 +297,30 @@
}, },
"tool-enable-mode": { "tool-enable-mode": {
"type": "enum", "type": "enum",
"values": ["disabled", "lo-hi", "hi-lo", "tri-lo", "tri-hi", "lo-tri", "values": [
"hi-tri"], "disabled",
"lo-hi",
"hi-lo",
"tri-lo",
"tri-hi",
"lo-tri",
"hi-tri"
],
"default": "lo-hi", "default": "lo-hi",
"code": "eom", "code": "eom",
"pin": 15 "pin": 15
}, },
"tool-direction-mode": { "tool-direction-mode": {
"type": "enum", "type": "enum",
"values": ["disabled", "lo-hi", "hi-lo", "tri-lo", "tri-hi", "lo-tri", "values": [
"hi-tri"], "disabled",
"lo-hi",
"hi-lo",
"tri-lo",
"tri-hi",
"lo-tri",
"hi-tri"
],
"default": "lo-hi", "default": "lo-hi",
"code": "dom", "code": "dom",
"pin": 16 "pin": 16
@@ -342,12 +361,20 @@
"values": [ "values": [
"disabled", "disabled",
"connect-write", "connect-write",
"max-freq-read", "max-freq-fixed", "max-freq-read",
"freq-set", "freq-signed-set", "max-freq-fixed",
"stop-write", "forward-write", "reverse-write", "freq-set",
"freq-read", "freq-signed-read", "freq-actech-read", "freq-signed-set",
"freq-scaled-set",
"stop-write",
"forward-write",
"reverse-write",
"freq-read",
"freq-signed-read",
"freq-actech-read",
"status-read", "status-read",
"disconnect-write"], "disconnect-write"
],
"default": "disabled", "default": "disabled",
"code": "vt" "code": "vt"
}, },
@@ -406,8 +433,7 @@
"default": false "default": false
}, },
"dynamic-power": { "dynamic-power": {
"help": "help": "Adjust tool power based on velocity and feed rate. Useful for LASERs.",
"Adjust tool power based on velocity and feed rate. Useful for LASERs.",
"type": "bool", "type": "bool",
"default": false, "default": false,
"code": "dp" "code": "dp"
@@ -452,24 +478,45 @@
"outputs": { "outputs": {
"load-1": { "load-1": {
"type": "enum", "type": "enum",
"values": ["disabled", "lo-hi", "hi-lo", "tri-lo", "tri-hi", "lo-tri", "values": [
"hi-tri"], "disabled",
"lo-hi",
"hi-lo",
"tri-lo",
"tri-hi",
"lo-tri",
"hi-tri"
],
"default": "lo-hi", "default": "lo-hi",
"code": "1om", "code": "1om",
"pin": 2 "pin": 2
}, },
"load-2": { "load-2": {
"type": "enum", "type": "enum",
"values": ["disabled", "lo-hi", "hi-lo", "tri-lo", "tri-hi", "lo-tri", "values": [
"hi-tri"], "disabled",
"lo-hi",
"hi-lo",
"tri-lo",
"tri-hi",
"lo-tri",
"hi-tri"
],
"default": "lo-hi", "default": "lo-hi",
"code": "2om", "code": "2om",
"pin": 1 "pin": 1
}, },
"fault": { "fault": {
"type": "enum", "type": "enum",
"values": ["disabled", "lo-hi", "hi-lo", "tri-lo", "tri-hi", "lo-tri", "values": [
"hi-tri"], "disabled",
"lo-hi",
"hi-lo",
"tri-lo",
"tri-hi",
"lo-tri",
"hi-tri"
],
"default": "lo-hi", "default": "lo-hi",
"code": "fom", "code": "fom",
"pin": 21 "pin": 21

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 250 KiB

View File

@@ -1,5 +1,5 @@
{ {
"version": "1.0.9", "version": "1.2.1",
"settings": { "settings": {
"junction-accel": 200000, "junction-accel": 200000,
"max-deviation": 0.05, "max-deviation": 0.05,

View File

@@ -172,7 +172,7 @@ c.isSpriteMaterial?(t.diffuse.value=c.color,t.opacity.value=c.opacity,t.rotation
b.envMap,a.flipEnvMap.value=b.envMap&&b.envMap.isCubeTexture?-1:1,a.reflectivity.value=b.reflectivity,a.refractionRatio.value=b.refractionRatio,a.maxMipLevel.value=Ca.get(b.envMap).__maxMipLevel);b.lightMap&&(a.lightMap.value=b.lightMap,a.lightMapIntensity.value=b.lightMapIntensity);b.aoMap&&(a.aoMap.value=b.aoMap,a.aoMapIntensity.value=b.aoMapIntensity);if(b.map)var c=b.map;else b.specularMap?c=b.specularMap:b.displacementMap?c=b.displacementMap:b.normalMap?c=b.normalMap:b.bumpMap?c=b.bumpMap:b.roughnessMap? b.envMap,a.flipEnvMap.value=b.envMap&&b.envMap.isCubeTexture?-1:1,a.reflectivity.value=b.reflectivity,a.refractionRatio.value=b.refractionRatio,a.maxMipLevel.value=Ca.get(b.envMap).__maxMipLevel);b.lightMap&&(a.lightMap.value=b.lightMap,a.lightMapIntensity.value=b.lightMapIntensity);b.aoMap&&(a.aoMap.value=b.aoMap,a.aoMapIntensity.value=b.aoMapIntensity);if(b.map)var c=b.map;else b.specularMap?c=b.specularMap:b.displacementMap?c=b.displacementMap:b.normalMap?c=b.normalMap:b.bumpMap?c=b.bumpMap:b.roughnessMap?
c=b.roughnessMap:b.metalnessMap?c=b.metalnessMap:b.alphaMap?c=b.alphaMap:b.emissiveMap&&(c=b.emissiveMap);void 0!==c&&(c.isWebGLRenderTarget&&(c=c.texture),!0===c.matrixAutoUpdate&&c.updateMatrix(),a.uvTransform.value.copy(c.matrix))}function r(a,b){a.specular.value=b.specular;a.shininess.value=Math.max(b.shininess,1E-4);b.emissiveMap&&(a.emissiveMap.value=b.emissiveMap);b.bumpMap&&(a.bumpMap.value=b.bumpMap,a.bumpScale.value=b.bumpScale,1===b.side&&(a.bumpScale.value*=-1));b.normalMap&&(a.normalMap.value= c=b.roughnessMap:b.metalnessMap?c=b.metalnessMap:b.alphaMap?c=b.alphaMap:b.emissiveMap&&(c=b.emissiveMap);void 0!==c&&(c.isWebGLRenderTarget&&(c=c.texture),!0===c.matrixAutoUpdate&&c.updateMatrix(),a.uvTransform.value.copy(c.matrix))}function r(a,b){a.specular.value=b.specular;a.shininess.value=Math.max(b.shininess,1E-4);b.emissiveMap&&(a.emissiveMap.value=b.emissiveMap);b.bumpMap&&(a.bumpMap.value=b.bumpMap,a.bumpScale.value=b.bumpScale,1===b.side&&(a.bumpScale.value*=-1));b.normalMap&&(a.normalMap.value=
b.normalMap,a.normalScale.value.copy(b.normalScale),1===b.side&&a.normalScale.value.negate());b.displacementMap&&(a.displacementMap.value=b.displacementMap,a.displacementScale.value=b.displacementScale,a.displacementBias.value=b.displacementBias)}function v(a,b){a.roughness.value=b.roughness;a.metalness.value=b.metalness;b.roughnessMap&&(a.roughnessMap.value=b.roughnessMap);b.metalnessMap&&(a.metalnessMap.value=b.metalnessMap);b.emissiveMap&&(a.emissiveMap.value=b.emissiveMap);b.bumpMap&&(a.bumpMap.value= b.normalMap,a.normalScale.value.copy(b.normalScale),1===b.side&&a.normalScale.value.negate());b.displacementMap&&(a.displacementMap.value=b.displacementMap,a.displacementScale.value=b.displacementScale,a.displacementBias.value=b.displacementBias)}function v(a,b){a.roughness.value=b.roughness;a.metalness.value=b.metalness;b.roughnessMap&&(a.roughnessMap.value=b.roughnessMap);b.metalnessMap&&(a.metalnessMap.value=b.metalnessMap);b.emissiveMap&&(a.emissiveMap.value=b.emissiveMap);b.bumpMap&&(a.bumpMap.value=
b.bumpMap,a.bumpScale.value=b.bumpScale,1===b.side&&(a.bumpScale.value*=-1));b.normalMap&&(a.normalMap.value=b.normalMap,a.normalScale.value.copy(b.normalScale),1===b.side&&a.normalScale.value.negate());b.displacementMap&&(a.displacementMap.value=b.displacementMap,a.displacementScale.value=b.displacementScale,a.displacementBias.value=b.displacementBias);b.envMap&&(a.envMapIntensity.value=b.envMapIntensity)}console.log("THREE.WebGLRenderer","96");a=a||{};var y=void 0!==a.canvas?a.canvas:document.createElementNS("http://www.w3.org/1999/xhtml", b.bumpMap,a.bumpScale.value=b.bumpScale,1===b.side&&(a.bumpScale.value*=-1));b.normalMap&&(a.normalMap.value=b.normalMap,a.normalScale.value.copy(b.normalScale),1===b.side&&a.normalScale.value.negate());b.displacementMap&&(a.displacementMap.value=b.displacementMap,a.displacementScale.value=b.displacementScale,a.displacementBias.value=b.displacementBias);b.envMap&&(a.envMapIntensity.value=b.envMapIntensity)}a=a||{};var y=void 0!==a.canvas?a.canvas:document.createElementNS("http://www.w3.org/1999/xhtml",
"canvas"),x=void 0!==a.context?a.context:null,w=void 0!==a.alpha?a.alpha:!1,G=void 0!==a.depth?a.depth:!0,D=void 0!==a.stencil?a.stencil:!0,O=void 0!==a.antialias?a.antialias:!1,S=void 0!==a.premultipliedAlpha?a.premultipliedAlpha:!0,E=void 0!==a.preserveDrawingBuffer?a.preserveDrawingBuffer:!1,z=void 0!==a.powerPreference?a.powerPreference:"default",A=null,B=null;this.domElement=y;this.context=null;this.sortObjects=this.autoClearStencil=this.autoClearDepth=this.autoClearColor=this.autoClear=!0;this.clippingPlanes= "canvas"),x=void 0!==a.context?a.context:null,w=void 0!==a.alpha?a.alpha:!1,G=void 0!==a.depth?a.depth:!0,D=void 0!==a.stencil?a.stencil:!0,O=void 0!==a.antialias?a.antialias:!1,S=void 0!==a.premultipliedAlpha?a.premultipliedAlpha:!0,E=void 0!==a.preserveDrawingBuffer?a.preserveDrawingBuffer:!1,z=void 0!==a.powerPreference?a.powerPreference:"default",A=null,B=null;this.domElement=y;this.context=null;this.sortObjects=this.autoClearStencil=this.autoClearDepth=this.autoClearColor=this.autoClear=!0;this.clippingPlanes=
[];this.localClippingEnabled=!1;this.gammaFactor=2;this.physicallyCorrectLights=this.gammaOutput=this.gammaInput=!1;this.toneMappingWhitePoint=this.toneMappingExposure=this.toneMapping=1;this.maxMorphTargets=8;this.maxMorphNormals=4;var P=this,I=!1,F=null,L=null,M=null,Q=-1;var H=b=null;var U=!1;var V=null,Z=null,T=new aa,zc=new aa,Y=null,fa=0,X=y.width,N=y.height,W=1,cb=new aa(0,0,X,N),ha=new aa(0,0,X,N),ra=!1,pa=new od,ba=new Nf,qd=!1,Xd=!1,yc=new J,db=new p;try{w={alpha:w,depth:G,stencil:D,antialias:O, [];this.localClippingEnabled=!1;this.gammaFactor=2;this.physicallyCorrectLights=this.gammaOutput=this.gammaInput=!1;this.toneMappingWhitePoint=this.toneMappingExposure=this.toneMapping=1;this.maxMorphTargets=8;this.maxMorphNormals=4;var P=this,I=!1,F=null,L=null,M=null,Q=-1;var H=b=null;var U=!1;var V=null,Z=null,T=new aa,zc=new aa,Y=null,fa=0,X=y.width,N=y.height,W=1,cb=new aa(0,0,X,N),ha=new aa(0,0,X,N),ra=!1,pa=new od,ba=new Nf,qd=!1,Xd=!1,yc=new J,db=new p;try{w={alpha:w,depth:G,stencil:D,antialias:O,
premultipliedAlpha:S,preserveDrawingBuffer:E,powerPreference:z};y.addEventListener("webglcontextlost",d,!1);y.addEventListener("webglcontextrestored",e,!1);var C=x||y.getContext("webgl",w)||y.getContext("experimental-webgl",w);if(null===C){if(null!==y.getContext("webgl"))throw Error("Error creating WebGL context with your selected attributes.");throw Error("Error creating WebGL context.");}void 0===C.getShaderPrecisionFormat&&(C.getShaderPrecisionFormat=function(){return{rangeMin:1,rangeMax:1,precision:1}})}catch(Lg){console.error("THREE.WebGLRenderer: "+ premultipliedAlpha:S,preserveDrawingBuffer:E,powerPreference:z};y.addEventListener("webglcontextlost",d,!1);y.addEventListener("webglcontextrestored",e,!1);var C=x||y.getContext("webgl",w)||y.getContext("experimental-webgl",w);if(null===C){if(null!==y.getContext("webgl"))throw Error("Error creating WebGL context with your selected attributes.");throw Error("Error creating WebGL context.");}void 0===C.getShaderPrecisionFormat&&(C.getShaderPrecisionFormat=function(){return{rangeMin:1,rangeMax:1,precision:1}})}catch(Lg){console.error("THREE.WebGLRenderer: "+

View File

@@ -394,7 +394,7 @@
var inBrowser = typeof window !== 'undefined' && Object.prototype.toString.call(window) !== '[object Object]'; var inBrowser = typeof window !== 'undefined' && Object.prototype.toString.call(window) !== '[object Object]';
// detect devtools // detect devtools
var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; var devtools = false;
// UA sniffing for working around browser-specific quirks // UA sniffing for working around browser-specific quirks
var UA = inBrowser && window.navigator.userAgent.toLowerCase(); var UA = inBrowser && window.navigator.userAgent.toLowerCase();
@@ -9677,14 +9677,5 @@ var template = Object.freeze({
Vue.version = '1.0.17'; Vue.version = '1.0.17';
// devtools global hook
/* istanbul ignore next */
if (devtools) {
devtools.emit('init', Vue);
} else if ('development' !== 'production' && inBrowser && /Chrome\/\d+/.test(window.navigator.userAgent)) {
console.log('Download the Vue Devtools for a better development experience:\n' + 'https://github.com/vuejs/vue-devtools');
}
return Vue; return Vue;
})); }));

View File

@@ -4,6 +4,9 @@ body
[v-cloak] [v-cloak]
display none display none
.menu-link
z-index unset
tt tt
color #000 color #000
background #eee background #eee
@@ -32,22 +35,63 @@ tt
height 140px height 140px
padding 0 padding 0
.header-content .nav-header
max-width 90% padding-left 60px
margin auto display flex
text-align left
.estop .brand
float right display flex
margin 5px flex-direction row
align-self center
white-space nowrap
img
width 300px
height 15%
.version
font-size 18pt
color #777
display flex
flex-direction column
justify-content space-evenly
border-left #777 2px solid;
margin-left 15px
padding 0px 10px
font-weight bold
.upgrade-link
margin-left 20px
font-size 16pt
align-self center
color blue
.upgrade-attention
color red
font-size 18pt
align-self center
margin-left 5px
.pi-temp-warning
align-self center
font-size 30pt
font-family Audiowide
display inline
margin 0 30px
.left
color #444
.right
color #e5aa3d
.whitespace
flex-grow 1
.video .video
position relative position relative
float right
width 174px width 174px
height 130px height 130px
margin 2px 5px border 2px solid transparent
border 2px solid #fff
border-radius 5px border-radius 5px
&:hover &:hover
@@ -87,29 +131,9 @@ tt
width 100% width 100%
height 100% height 100%
.banner .estop
float left align-self center
padding-top 40px margin 0 30px
white-space nowrap
img
vertical-align top
.title
font-size 30pt
font-family Audiowide
display inline
margin-right 0.5em
.left
color #444
.right
color #e5aa3d
.subtitle
font-size 18pt
font-weight 100
color #aaa
.error .error
background red background red
@@ -214,8 +238,6 @@ span.unit
.pure-control-group .pure-control-group
label.units label.units
width 6em width 6em
label.units
text-align left text-align left
textarea textarea
@@ -231,6 +253,7 @@ span.unit
border-radius 3px border-radius 3px
display inline-block display inline-block
@keyframes blink @keyframes blink
50% 50%
fill #ff9d00 fill #ff9d00
@@ -267,6 +290,12 @@ span.unit
// The jogging buttons, etc. // The jogging buttons, etc.
.control-buttons button .control-buttons button
font-size 150% font-size 150%
width 100px
height 100px
.jog-units
font-size initial
margin-left 5px
&:first-child &:first-child
margin 0.5em 0 margin 0.5em 0
@@ -335,6 +364,7 @@ span.unit
.axis .axis
.name .name
text-transform capitalize text-transform capitalize
vertical-align middle
.name, .position .name, .position
font-size 24pt font-size 24pt
@@ -421,7 +451,7 @@ span.unit
min-width 8em min-width 8em
width 100% width 100%
.mach_units .units
padding 0 padding 0
select select
@@ -848,18 +878,6 @@ tt.save
text-decoration none text-decoration none
.upgrade-version
display inline-block
border-radius 4px
padding 2px
margin-left 0.5em
color #555
background-color #e5aa3d
text-decoration none
&:hover
color #fff
.modal-mask .modal-mask
position fixed position fixed
z-index 9998 z-index 9998

24
src/svelte-components/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,48 @@
# Svelte + TS + Vite
This template should help get you started developing with Svelte and TypeScript in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
## Need an official Svelte framework?
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
## Technical considerations
**Why use this over SvelteKit?**
- It brings its own routing solution which might not be preferable for some users.
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
`vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
**Why include `.vscode/extensions.json`?**
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
**Why enable `allowJs` in the TS template?**
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
**Why is HMR not preserving my local component state?**
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
```ts
// store.ts
// An extremely simple external store
import { writable } from 'svelte/store'
export default writable(0)
```

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svelte + TS + Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

10382
src/svelte-components/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
{
"name": "svelte-components",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"postbuild": "smui-theme compile dist/smui.css -i src/theme",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/kit": "^1.0.0-next.392",
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"@tsconfig/svelte": "^3.0.0",
"node-sass": "^7.0.1",
"polyfill-object.fromentries": "^1.0.1",
"smui-theme": "^6.0.0-beta.16",
"string.prototype.matchall": "^4.0.7",
"svelte": "^3.49.0",
"svelte-check": "^2.8.0",
"svelte-icon": "^1.2.4",
"svelte-material-ui": "^6.0.0-beta.16",
"svelte-preprocess": "^4.10.7",
"svelte-tiny-virtual-list": "^2.0.5",
"tslib": "^2.4.0",
"typescript": "^4.7.4",
"vite": "^3.0.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,227 @@
<script lang="ts">
import WifiConnectionDialog from "$dialogs/WifiConnectionDialog.svelte";
import ChangeHostnameDialog from "$dialogs/ChangeHostnameDialog.svelte";
import Button, { Label } from "@smui/button";
import List, { Item, Graphic, Text, Meta } from "@smui/list";
import Card from "@smui/card";
import { networkInfo, type WifiNetwork } from "$lib/NetworkInfo";
import {GET} from "$lib/api"
import {processNetworkInfo} from "$lib/NetworkInfo"
let networkData = GET("network")
networkData.then(value=>processNetworkInfo(value))
let changeHostnameDialog = {
open: false,
};
let wifiConnectionDialog = {
open: false,
network: {} as WifiNetwork,
};
function refreshWifi(){
let networkData = GET("network")
networkData.then(value=>processNetworkInfo(value))
}
function getWifiStrengthStyle(network: WifiNetwork) {
const strength = Math.ceil((Number(network.Quality) / 100) * 4);
switch (strength) {
case 0:
return "clip-path: circle(0px at 12.5px 19px);";
case 1:
return "clip-path: circle(4px at 12.5px 19px);";
case 2:
return "clip-path: circle(8px at 12.5px 19px);";
case 3:
return "clip-path: circle(14px at 12.5px 19px);";
case 4:
return "";
}
}
function onChangeHostname() {
changeHostnameDialog = {
open: true,
};
}
function onNetworkSelected(network: WifiNetwork) {
wifiConnectionDialog = {
open: true,
network,
};
}
</script>
<WifiConnectionDialog {...wifiConnectionDialog} />
<ChangeHostnameDialog {...changeHostnameDialog} />
<div class="admin-network-view">
<div style="display:flex; flex-direction:row; justify-content:space-between" >
<h1>Network Info</h1>
<div style="text-align: center; padding:20px">
<Button on:click={refreshWifi} touch variant="raised">
<Label>Refresh WiFi</Label>
</Button>
</div>
</div>
<div class="pure-form pure-form-aligned">
<div class="pure-control-group">
<label for="hostname">Hostname</label>
<Card id="hostname" variant="outlined">
<Text id="hostname">
{$networkInfo.hostname}
</Text>
</Card>
<Button on:click={onChangeHostname} touch variant="raised">
<Label>Change</Label>
</Button>
</div>
</div>
<div class="pure-form pure-form-aligned">
<div class="pure-control-group">
<label for="ip-addresses">IP Addresses</label>
<Card id="ip-addresses" variant="outlined">
<!-- {#each $networkInfo.ipAddresses as ipAddress} -->
<div>
<Text id="hostname">
{$networkInfo.ipAddresses}
</Text>
</div>
<!-- {/each} -->
</Card>
</div>
</div>
<div class="pure-form pure-form-aligned">
<div class="pure-control-group">
<label for="wifi">Wi-Fi</label>
<div class="wifi-networks">
<Card id="wifi" variant="outlined">
<List>
{#if $networkInfo.wifi.networks.length === 0}
<Item class="wifi-network">
<Text>Scanning...</Text>
</Item>
{:else}
{#each $networkInfo.wifi.networks as network}
<Item
class="wifi-network"
on:SMUI:action={() =>
onNetworkSelected(network)}
>
<Graphic
class="strength {$networkInfo.wifi
.ssid === network.Name
? 'active'
: ''}"
>
<span class="fa fa-wifi background" />
<span
class="fa fa-wifi"
style={getWifiStrengthStyle(
network
)}
/>
</Graphic>
<Text style="margin-right: 20px;"
>{network.Name}</Text
>
{#if network.Encryption !== "Open"}
<Meta>
<span class="fa fa-lock" />
</Meta>
{/if}
</Item>
{/each}
{/if}
</List>
</Card>
<em style="display: block;">
Click on a Wi-Fi network to connect or disconnect.
</em>
</div>
</div>
</div>
</div>
<style lang="scss">
$primary: #0078e7;
$very-dark: #555;
$text: #777;
$grey: #bbb;
$light: #ddd;
:global {
.admin-network-view {
.pure-form-aligned .pure-control-group label {
vertical-align: top;
font-size: 15pt;
font-weight: bold;
}
button {
margin: 0;
}
.mdc-card {
width: 400px;
min-height: 38px;
display: inline-block;
vertical-align: top;
margin-bottom: 20px;
margin-right: 20px;
padding: 5px 15px;
}
.wifi-networks {
display: inline-block;
.mdc-card {
padding: 0;
margin-bottom: 5px;
}
}
.wifi-network {
.lock {
font-size: 20px;
vertical-align: text-bottom;
}
.strength {
border-radius: 50%;
padding: 3px;
background-color: $light;
color: $very-dark;
margin-right: 10px;
position: relative;
&.active {
background-color: $primary;
color: white;
}
span {
position: absolute;
top: 5px;
font-size: 22px;
&.background {
opacity: 0.25;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,195 @@
<script lang="ts">
import configTemplate from "../../../resources/config-template.json";
import { Config, DisplayUnits } from "$lib/ConfigStore";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
import { onMount } from "svelte";
type ValueType =
| string
| number
| { title: string; value: string | number };
type Template = {
type?: string;
values?: Array<ValueType>;
unit?: "string";
iunit?: "string";
min?: number;
max?: number;
step?: number;
help?: string;
default?: string | number;
scale?: number;
};
const namesByKey = {
"units":"units",
"probing-prompts": "Show safety prompts",
"probe-xdim": "Probe block width",
"probe-ydim": "Probe block length",
"probe-zdim": "Probe block height",
"probe-fast-seek": "Fast seek speed",
"probe-slow-seek": "Slow seek speed",
"program-start": "On program start",
"tool-change": "On tool change",
"program-end": "On program end",
"max-deviation": "Maximum deviation",
"junction-accel": "Junction acceleration",
};
export let key: string;
let keyParts: string[];
let template: Template;
let name: string;
let title: string;
let units: string;
let value;
onMount(() => {
keyParts = (key || "").split(".");
template = getTemplate();
title = getTitle();
name = keyParts[keyParts.length - 1];
name = namesByKey[name] || name;
value = getValue();
});
$: metric = $DisplayUnits === "METRIC";
$: if (template) {
units = metric || !template.iunit ? template.unit : template.iunit;
}
function getTemplate(): Template {
let template = configTemplate;
for (const part of keyParts) {
template = template[part];
}
return template as Template;
}
function getTitle(): string {
const help = (!!template.help) ? `${template.help}\n` : "";
return `${template.help}\n Default: ${template.default} ${template.unit || ""}`;
}
function getValue(): string | number {
let value: any = $Config;
for (const part of keyParts) {
value = value[part];
}
if (template.scale) {
if (metric) {
return Number.parseFloat(value.toFixed(3));
}
return Number.parseFloat((value / template.scale).toFixed(4));
}
return value;
}
function onChange(event) {
Config.update((config) => {
let target = config;
for (const part of keyParts.slice(0, -1)) {
target = target[part];
}
const value = getValueFromElement(event.target);
target[keyParts[keyParts.length - 1]] = value;
return config;
});
ControllerMethods.dispatch("config-changed");
}
function getValueFromElement(element) {
switch (template.type) {
case "float":
case "int":
return Number(element.value);
case "bool":
return element.checked;
default:
return element.value;
}
}
function getOptionValue(opt: ValueType) {
switch (typeof opt) {
case "object":
return opt.value || opt;
default:
return opt;
}
}
function getOptionTitle(opt: ValueType) {
switch (typeof opt) {
case "object":
return opt.title || opt;
default:
return opt;
}
}
</script>
{#if template}
<div class="pure-control-group" {title}>
<label for={name}>{name}</label>
{#if template.values}
<select {name} bind:value on:change={onChange}>
{#each template.values as opt}
<option
value={getOptionValue(opt)}
disabled={opt === "-----"}
>
{getOptionTitle(opt)}
</option>
{/each}
</select>
{:else if template.type === "bool"}
<input
{name}
type="checkbox"
checked={value}
on:change={onChange}
/>
{:else if template.type === "float"}
<input
{name}
type="number"
min={template.min}
max={template.max}
step={template.step || "any"}
bind:value
on:keyup={onChange}
/>
{:else if template.type === "int"}
<input
{name}
type="number"
min={template.min}
max={template.max}
bind:value
on:keyup={onChange}
/>
{:else if template.type === "string"}
<input {name} type="text" bind:value on:keyup={onChange} />
{:else if template.type == "text"}
<textarea {name} bind:value on:keyup={onChange} />
{/if}
<label for="" class="units">{units || ""}</label>
<slot name="extra" />
</div>
{/if}

View File

@@ -0,0 +1,62 @@
<script lang="ts">
import RemoteDiagnosticsDialog from "$dialogs/RemoteDiagnosticsDialog.svelte";
import Button, { Label } from "@smui/button";
let showRemoteDiagnosticsDialog = false;
</script>
<RemoteDiagnosticsDialog bind:open={showRemoteDiagnosticsDialog} />
<h2>Support & Contact Info</h2>
<p>
Please visit
<a href="https://onefinitycnc.com/support" target="_blank">
onefinitycnc.com/support
</a>
for a variety of support resources, and to find our contact information.
</p>
<!-- <Button
touch
variant="raised"
on:click={() => (showRemoteDiagnosticsDialog = true)}
>
<Label>Remote Diagnostics</Label>
</Button> -->
<h2>Discussion Forum</h2>
<p>
Check out our support and discussion forum at
<a href="https://forum.onefinitycnc.com" target="_blank"
>forum.onefinitycnc.com</a
>. Register on the site and post a message. We can't wait to hear from you.
</p>
<p>
We also maintain a list of
<a
href="https://forum.onefinitycnc.com/t/what-cad-cam-software-can-be-used-to-make-gcode-files-for-the-onefinity-cnc/10253"
target="_blank"
>
recommended software packages
</a>
on the forum.
</p>
<h2>Credits & Acknowledgements</h2>
<h4 style="margin-bottom: 0;">Artwork</h4>
<p style="margin-top: 0;">
Special thanks to
<a href="https://www.instagram.com/fierysquirrelart/" target="_blank">
@fierysquirrelart
</a>
for many of the graphics used in the controller.
</p>
<h4 style="margin-bottom: 0;">Buildbotics</h4>
<p style="margin-top: 0;">
This controller is based on the
<a href="http://buildbotics.com" target="_blank"
>Buildbotics CNC Controller</a
>.
</p>

View File

@@ -0,0 +1,129 @@
<script lang="ts">
import configTemplate from "../../../resources/config-template.json";
import ScreenRotationDialog from "$dialogs/ScreenRotationDialog.svelte";
import ConfigTemplatedInput from "./ConfigTemplatedInput.svelte";
import SetTimeDialog from "$dialogs/SetTimeDialog.svelte";
import Button, { Label } from "@smui/button";
const gcodeURL = "https://linuxcnc.org/docs/html/gcode/g-code.html";
let showScreenRotationDialog = false;
let showSetTimeDialog = false;
</script>
<ScreenRotationDialog bind:open={showScreenRotationDialog} />
<!-- <SetTimeDialog bind:open={showSetTimeDialog} /> -->
<div class="settings-view">
<h1>Settings</h1>
<div class="pure-form pure-form-aligned">
<h2>User Interface</h2>
<fieldset>
<div class="pure-control-group">
<label for="screen-rotation" />
<Button
name="screen-rotation"
touch
variant="raised"
on:click={() => (showScreenRotationDialog = true)}
>
<Label>Change Screen Rotation</Label>
</Button>
</div>
<!-- <div class="pure-control-group">
<label for="set-time" />
<Button
name="set-time"
touch
variant="raised"
on:click={() => (showSetTimeDialog = true)}
>
<Label>Change Time & Timezone</Label>
</Button>
</div> -->
</fieldset>
<h2>Units</h2>
<fieldset>
<ConfigTemplatedInput key={`settings.units`} />
<div class="tip">
Note, units sets both the machine default units and the units used in motor configuration. GCode program-start,
set below, may also change the default machine units.
</div>
</fieldset>
<h2>Probing</h2>
<fieldset>
<ConfigTemplatedInput key={`settings.probing-prompts`} />
<div class="tip">
Onefinity highly recommends that you keep the safety prompts
enabled. If you choose to live dangerously, and disable the
safety prompts, Onefinity cannot be held responsible.
</div>
<br />
{#each Object.keys(configTemplate.probe) as key}
{#if key !== "probe-diameter"}
<ConfigTemplatedInput key={`probe.${key}`} />
{/if}
{/each}
</fieldset>
<fieldset>
<h2>GCode</h2>
{#each Object.keys(configTemplate.gcode) as key}
<ConfigTemplatedInput key={`gcode.${key}`} />
{/each}
</fieldset>
<h2>Path Accuracy</h2>
<fieldset>
<ConfigTemplatedInput key={`settings.max-deviation`} />
<div class="tip">
Lower the maximum deviation to follow the programmed path more
precisely but at a slower speed.
</div>
<div class="tip">
In order to improve traversal speed, the path planner may merge
consecutive moves or round off sharp corners if doing so would
deviate from the program path by less than the maximum
deviation.
</div>
<div class="tip">
GCode commands
<a href={`${gcodeURL}#gcode:g61`} target="_blank">G61, G61.1</a>
and <a href={`${gcodeURL}#gcode:g64`} target="_blank">G64</a> also
affect path planning accuracy.
</div>
</fieldset>
<h2>Cornering Speed (Advanced)</h2>
<fieldset>
<ConfigTemplatedInput key={`settings.junction-accel`} />
<div class="tip">
Junction acceleration limits the cornering speed the planner
will allow. Increasing this value will allow for faster
traversal of corners but may cause the planner to violate axis
jerk limits and stall the motors. Use with caution.
</div>
</fieldset>
</div>
</div>
<style lang="scss">
.settings-view {
.tip {
margin-left: 210px;
margin-bottom: 15px;
font-style: italic;
font-size: 90%;
line-height: 1.5;
}
}
</style>

View File

@@ -0,0 +1,90 @@
<script lang="ts">
import TextField from "@smui/textfield";
import Icon from "@smui/textfield/icon";
import HelperText from "@smui/textfield/helper-text";
import MenuSurface, {
type MenuSurfaceComponentDev,
} from "@smui/menu-surface";
import List, { Item, Text } from "@smui/list";
import { virtualKeyboardChange } from "$lib/CustomActions";
import { onDestroy } from "svelte";
let menuSurface: MenuSurfaceComponentDev;
let menuTimeout;
let optionSelected: boolean = false;
export let value: string;
export let options: string[][];
export let valid: boolean;
export let helperText: string;
onDestroy(() => {
if (menuTimeout) {
clearTimeout(menuTimeout);
menuTimeout = undefined;
}
});
function showMenu(show: boolean) {
if (show && optionSelected) {
return;
}
optionSelected = false;
if (menuTimeout) {
clearTimeout(menuTimeout);
}
// Use a timeout to "debounce" the display of the menu.
menuTimeout = setTimeout(() => menuSurface.setOpen(show), 100);
}
</script>
<div class="textfield-with-options">
<TextField
bind:value
on:focusin={() => showMenu(true)}
on:focusout={() => showMenu(false)}
use={[virtualKeyboardChange((v) => (value = v))]}
{...$$restProps}
>
<div slot="trailingIcon">
{#if valid}
<Icon class="fa fa-check-circle-o" style="color: green;" />
{/if}
</div>
<HelperText persistent slot="helper">{helperText}</HelperText>
</TextField>
<MenuSurface bind:this={menuSurface} anchorCorner="BOTTOM_LEFT">
<div style="display: flex; flex-direction: row;">
{#each options as group}
<List>
{#each group as option}
<Item
on:SMUI:action={() => {
value = option;
showMenu(false);
optionSelected = true;
}}
>
<Text>{option}</Text>
</Item>
{/each}
</List>
{/each}
</div>
</MenuSurface>
</div>
<style lang="scss">
:global {
.textfield-with-options {
.mdc-deprecated-list-item {
height: 32px;
}
}
}
</style>

View File

@@ -0,0 +1,101 @@
<script lang="ts">
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import Button, { Label } from "@smui/button";
import TextField from "@smui/textfield";
import MessageDialog from "$dialogs/MessageDialog.svelte";
import * as api from "$lib/api";
import { virtualKeyboardChange } from "$lib/CustomActions";
// https://man7.org/linux/man-pages/man7/hostname.7.html
//
// Each element of the hostname must be from 1 to 63 characters long
// and the entire hostname, including the dots, can be at most 253
// characters long. Valid characters for hostnames are ASCII(7)
// letters from a to z, the digits from 0 to 9, and the hyphen (-).
// A hostname may not start with a hyphen.
const pattern = /[a-zA-Z0-9][a-zA-Z0-9-]{0,62}/;
export let open = false;
let rebooting = false;
let redirectTimeout = 45;
let hostname = "";
$: setTimeout(() => {
hostname = (hostname.match(pattern) || [""])[0].toLowerCase();
}, 0);
$: if (open) {
hostname = "";
}
async function onConfirm() {
rebooting = true;
await api.PUT("hostname", { hostname });
await api.PUT("reboot");
const interval = setInterval(() => {
if (0 < redirectTimeout) {
redirectTimeout -= 1;
} else {
clearInterval(interval);
location.hostname = getRedirectTarget();
}
}, 1000);
}
function getRedirectTarget() {
if (location.hostname.endsWith(".local")) {
return `${hostname}.local`;
}
if (location.hostname.endsWith(".lan")) {
return `${hostname}.lan`;
}
return hostname;
}
</script>
<MessageDialog open={rebooting} title="Rebooting" noaction>
Rebooting to apply the hostname change...
</MessageDialog>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="change-hostname-dialog-title"
aria-describedby="change-hostname-dialog-content"
>
<Title id="change-hostname-dialog-title">Change Hostname</Title>
<Content id="change-hostname-dialog-content">
<TextField
bind:value={hostname}
use={[InitialFocus, virtualKeyboardChange((v) => (hostname = v))]}
label="New Hostname"
spellcheck="false"
variant="filled"
style="width: 100%;"
/>
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button
defaultAction
on:click={onConfirm}
disabled={hostname.length === 0}
>
<Label>Confirm & Reboot</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,244 @@
<script lang="ts" context="module">
import { writable } from "svelte/store";
import HomeMachineDialog from "$dialogs/HomeMachineDialog.svelte";
import ProbeDialog from "$dialogs/ProbeDialog.svelte";
import ScreenRotationDialog from "$dialogs/ScreenRotationDialog.svelte";
import UploadDialog from "$dialogs/UploadDialog.svelte";
import SetTimeDialog from "./SetTimeDialog.svelte";
import ManualHomeAxisDialog from "./ManualHomeAxisDialog.svelte";
import SetAxisPositionDialog from "./SetAxisPositionDialog.svelte";
import MoveToZeroDialog from "./MoveToZeroDialog.svelte";
import ShutdownDialog from "./ShutdownDialog.svelte";
import MessageDialog from "./MessageDialog.svelte";
const HomeMachineDialogProps = writable<HomeMachineDialogPropsType>();
type HomeMachineDialogPropsType = {
open: boolean;
home: () => void;
};
const ProbeDialogProps = writable<ProbeDialogPropsType>();
type ProbeDialogPropsType = {
open: boolean;
probeType: "xyz" | "z";
};
const ScreenRotationDialogProps = writable<ScreenRotationDialogPropsType>();
type ScreenRotationDialogPropsType = {
open: boolean;
};
const UploadDialogProps = writable<UploadDialogPropsType>();
type UploadDialogPropsType = {
open: boolean;
file: File;
onComplete: () => void;
};
const SetTimeDialogProps = writable<SetTimeDialogPropsType>();
type SetTimeDialogPropsType = {
open: boolean;
};
const ManualHomeAxisDialogProps = writable<ManualHomeAxisDialogPropsType>();
type ManualHomeAxisDialogPropsType = {
open: boolean;
axis: string;
};
const SetAxisPositionDialogProps =
writable<SetAxisPositionDialogPropsType>();
type SetAxisPositionDialogPropsType = {
open: boolean;
axis: string;
};
const MoveToZeroDialogProps = writable<MoveToZeroDialogPropsType>();
type MoveToZeroDialogPropsType = {
open: boolean;
axes: "xy" | "z";
};
const ShutdownDialogProps = writable<ShutdownDialogPropsType>();
type ShutdownDialogPropsType = {
open: boolean;
};
const MessageDialogProps = writable<MessageDialogPropsType>();
type MessageDialogPropsType = {
open: boolean;
title: string;
message: string;
noaction: boolean;
};
export function showDialog(
dialog: "HomeMachine",
props: Omit<HomeMachineDialogPropsType, "open">
);
export function showDialog(
dialog: "Probe",
props: Omit<ProbeDialogPropsType, "open">
);
export function showDialog(
dialog: "ScreenRotation",
props: Omit<ScreenRotationDialogPropsType, "open">
);
export function showDialog(
dialog: "Upload",
props: Omit<UploadDialogPropsType, "open">
);
export function showDialog(
dialog: "SetTime",
props: Omit<SetTimeDialogPropsType, "open">
);
export function showDialog(
dialog: "ManualHomeAxis",
props: Omit<ManualHomeAxisDialogPropsType, "open">
);
export function showDialog(
dialog: "SetAxisPosition",
props: Omit<SetAxisPositionDialogPropsType, "open">
);
export function showDialog(
dialog: "MoveToZero",
props: Omit<MoveToZeroDialogPropsType, "open">
);
export function showDialog(
dialog: "Shutdown",
props: Omit<ShutdownDialogPropsType, "open">
);
export function showDialog(
dialog: "Message",
props: Omit<MessageDialogPropsType, "open">
);
export function showDialog(dialog: string, props: any) {
switch (dialog) {
case "HomeMachine":
HomeMachineDialogProps.set({ ...props, open: true });
break;
case "Probe":
ProbeDialogProps.set({ ...props, open: true });
break;
case "ScreenRotation":
ScreenRotationDialogProps.set({ ...props, open: true });
break;
case "Upload":
UploadDialogProps.set({ ...props, open: true });
break;
case "SetTime":
SetTimeDialogProps.set({ ...props, open: true });
break;
case "ManualHomeAxis":
ManualHomeAxisDialogProps.set({ ...props, open: true });
break;
case "SetAxisPosition":
SetAxisPositionDialogProps.set({ ...props, open: true });
break;
case "MoveToZero":
MoveToZeroDialogProps.set({ ...props, open: true });
break;
case "Shutdown":
ShutdownDialogProps.set({ ...props, open: true });
break;
case "Message":
MessageDialogProps.set({ ...props, open: true });
break;
default:
throw new Error(`Unknown dialog '${dialog}'`);
}
}
</script>
<script lang="ts">
import { onMount, onDestroy } from "svelte";
let bodyObserver: MutationObserver;
let keyboardObserver: MutationObserver;
onMount(() => {
bodyObserver = new MutationObserver(() => {
const virtualKeyboard = document.getElementById(
"virtualKeyboardChromeExtension"
);
if (virtualKeyboard) {
bodyObserver.disconnect();
bodyObserver = undefined;
const virtualKeyboardOverlay = document.getElementById(
"virtualKeyboardChromeExtensionOverlayScrollExtend"
);
keyboardObserver = new MutationObserver(() => {
const open =
virtualKeyboard.getAttribute("_state") === "open";
const keyboardHeight = Number.parseFloat(
virtualKeyboardOverlay.style.height
);
const dialogContainers =
document.querySelectorAll<HTMLDivElement>(
".mdc-dialog .mdc-dialog__container"
);
for (let dialogContainer of dialogContainers) {
dialogContainer.style["marginBottom"] = open
? `${keyboardHeight}px`
: "";
}
});
keyboardObserver.observe(virtualKeyboard, { attributes: true });
}
});
bodyObserver.observe(document.querySelector("body"), {
subtree: false,
childList: true,
});
});
onDestroy(() => {
if (bodyObserver) {
bodyObserver.disconnect();
bodyObserver = undefined;
}
if (keyboardObserver) {
keyboardObserver.disconnect();
keyboardObserver = undefined;
}
});
</script>
<HomeMachineDialog {...$HomeMachineDialogProps} />
<ProbeDialog {...$ProbeDialogProps} />
<ScreenRotationDialog {...$ScreenRotationDialogProps} />
<UploadDialog {...$UploadDialogProps} />
<SetTimeDialog {...$SetTimeDialogProps} />
<ManualHomeAxisDialog {...$ManualHomeAxisDialogProps} />
<SetAxisPositionDialog {...$SetAxisPositionDialogProps} />
<MoveToZeroDialog {...$MoveToZeroDialogProps} />
<ShutdownDialog {...$ShutdownDialogProps} />
<MessageDialog {...$MessageDialogProps} />

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import Button, { Label } from "@smui/button";
export let open;
export let home: () => any;
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="home-machine-dialog-title"
aria-describedby="home-machine-dialog-content"
>
<Title id="home-machine-dialog-title">Home Machine</Title>
<Content id="home-machine-dialog-content">Home the machine?</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button defaultAction use={[InitialFocus]} on:click={home}>
<Label>OK</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,54 @@
<script lang="ts">
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import TextField from "@smui/textfield";
import Button, { Label } from "@smui/button";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
import { virtualKeyboardChange } from "$lib/CustomActions";
export let open: boolean;
export let axis = "";
let value = 0;
function onConfirm() {
ControllerMethods.set_home(axis, value);
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="manual-home-axis-dialog-title"
aria-describedby="manual-home-axis-dialog-content"
>
<Title id="manual-home-axis-dialog-title">
Manually Home {axis.toUpperCase()} Axis
</Title>
<Content id="manual-home-axis-dialog-content">
<p>Set axis absolute position</p>
<TextField
label="Absolute"
type="number"
bind:value
use={[InitialFocus, virtualKeyboardChange((v) => (value = v))]}
variant="filled"
style="width: 100%;"
/>
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button defaultAction on:click={onConfirm}>
<Label>Set</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import Button, { Label } from "@smui/button";
export let open: boolean;
export let title = "";
export let message = "";
export let noaction = false;
</script>
<Dialog
bind:open
scrimClickAction=""
escapeKeyAction=""
aria-labelledby="message-dialog-title"
aria-describedby="message-dialog-content"
>
<Title id="message-dialog-title">{title}</Title>
<Content id="message-dialog-content">
<slot>{message}</slot>
</Content>
{#if !noaction}
<Actions>
<Button defaultAction use={[InitialFocus]}>
<Label>OK</Label>
</Button>
</Actions>
{/if}
</Dialog>

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import Dialog, { Title, Actions, InitialFocus } from "@smui/dialog";
import Button, { Label } from "@smui/button";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
export let open;
export let axes: "xy" | "z";
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="move-to-zero-dialog-title"
aria-describedby="move-to-zero-dialog-content"
>
<Title id="move-to-zero-dialog-title">
Move to {(axes || "").toUpperCase()} origin?
</Title>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button
defaultAction
use={[InitialFocus]}
on:click={() => ControllerMethods.gotoZero(axes)}
>
<Label>Confirm</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,517 @@
<script type="ts">
import Dialog, { Title, Content, Actions } from "@smui/dialog";
import Button, { Label } from "@smui/button";
import LinearProgress from "@smui/linear-progress";
import { waitForChange } from "$lib/StoreHelpers";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
import { Config } from "$lib/ConfigStore";
import { writable, type Writable } from "svelte/store";
import {
probingActive,
probeContacted,
probingComplete,
probingFailed,
probingStarted,
} from "$lib/ControllerState";
import { numberWithUnit } from "$lib/RegexHelpers";
import TextFieldWithOptions from "$components/TextFieldWithOptions.svelte";
import Icon from "svelte-icon";
import BitDiameter from "../svgs/probe-bit-diameter.svg?raw";
import CheckXYZ from "../svgs/probe-check-xyz.svg?raw";
import CheckZ from "../svgs/probe-check-z.svg?raw";
import PlaceXYZ from "../svgs/probe-place-xyz.svg?raw";
import PlaceZ from "../svgs/probe-place-z.svg?raw";
import PutAwayXYZ from "../svgs/probe-put-away-xyz.svg?raw";
import PutAwayZ from "../svgs/probe-put-away-z.svg?raw";
const ValidSteps = [
"None",
"CheckProbe",
"BitDimensions",
"PlaceProbeBlock",
"Probe",
"Done",
] as const;
type Step = typeof ValidSteps[number];
function isStep(str): str is Step {
return ValidSteps.includes(str);
}
const stepLabels: Record<Step, string> = {
None: "",
CheckProbe: "Check probe",
BitDimensions: "Bit dimensions",
PlaceProbeBlock: "Place probe block",
Probe: "Probe",
Done: "Done",
};
const cancelled = writable(false);
const userAcknowledged = writable(false);
const imperialBits: `${number}/${number} in`[] = [
"1/2 in",
"3/8 in",
"1/4 in",
"1/8 in",
"1/16 in",
"1/32 in",
];
const metricBits: `${number} mm`[] = [
"12 mm",
"10 mm",
"8 mm",
"6 mm",
"4 mm",
"3 mm",
];
export let open;
export let probeType: "xyz" | "z";
let currentStep: Step = "None";
let cutterDiameterString: string = "";
let cutterDiameterMetric: number;
let showCancelButton = true;
let steps: Step[] = [];
let nextButton = {
label: "Next",
disabled: false,
allowClose: false,
};
$: metric = $Config.settings?.units === "METRIC";
$: cutterDiameterMetric = numberWithUnit
.parse(cutterDiameterString)
?.toMetric();
$: if (open) {
cutterDiameterString = localStorage.getItem("cutterDiameter") ?? "";
// Svelte appears not to like it when you invoke
// an async function from a reactive statement, so we
// use requestAnimationFrame to call 'begin' at a later moment.
requestAnimationFrame(begin);
}
$: if (cutterDiameterString) {
updateButtons();
}
async function begin() {
try {
$probingActive = true;
assertValidProbeType();
$probingFailed = false;
const enableSafety = $Config.settings["probing-prompts"];
steps = [
enableSafety ? "CheckProbe" : undefined,
probeType === "xyz" ? "BitDimensions" : undefined,
enableSafety ? "PlaceProbeBlock" : undefined,
"Probe",
"Done",
].filter<Step>(isStep);
await stepCompleted("CheckProbe", probeContacted);
if (probeType === "xyz") {
await stepCompleted("BitDimensions", userAcknowledged);
localStorage.setItem(
"cutterDiameter",
numberWithUnit.normalize(cutterDiameterString)
);
}
await stepCompleted("PlaceProbeBlock", userAcknowledged);
await stepCompleted("Probe", probingComplete, probingFailed);
await stepCompleted("Done", userAcknowledged);
if (probeType === "xyz") {
ControllerMethods.gotoZero("xy");
}
} catch (err) {
if (err.message !== "cancelled") {
console.error("Error during probing:", err);
}
} finally {
$probingActive = false;
currentStep = "None";
if ($probingStarted) {
ControllerMethods.stop();
}
clearFlags();
}
}
function assertValidProbeType() {
switch (probeType) {
case "xyz":
case "z":
break;
default:
throw new Error(`Invalid probe type: ${probeType}`);
}
}
async function stepCompleted(
nextStep: Step,
...writables: Array<Writable<any>>
) {
currentStep = nextStep;
if (!steps.includes(currentStep)) {
return;
}
clearFlags();
updateButtons();
if (currentStep === "Probe") {
executeProbe();
}
await Promise.race([
...writables.map((writable) => waitForChange(writable)),
waitForChange(cancelled),
]);
if ($cancelled) {
throw new Error("cancelled");
}
}
function clearFlags(foo: string = "") {
$cancelled = false;
$probeContacted = false;
$probingStarted = false;
$probingComplete = false;
$userAcknowledged = false;
}
function updateButtons() {
showCancelButton = true;
nextButton = {
label: "Next",
disabled: false,
allowClose: false,
};
switch (currentStep) {
case "CheckProbe":
case "Probe":
nextButton.disabled = true;
break;
case "BitDimensions":
nextButton.disabled = !isFinite(cutterDiameterMetric);
break;
case "Done":
showCancelButton = false;
nextButton = {
disabled: false,
label: "Done",
allowClose: true,
};
break;
}
}
function executeProbe() {
const probeBlockWidth = $Config.probe["probe-xdim"];
const probeBlockLength = $Config.probe["probe-ydim"];
const probeBlockHeight = $Config.probe["probe-zdim"];
const slowSeek = $Config.probe["probe-slow-seek"];
const fastSeek = $Config.probe["probe-fast-seek"];
const cutterLength = 12.7;
const zLift = 1;
const xOffset = probeBlockWidth + cutterDiameterMetric / 2.0;
const yOffset = probeBlockLength + cutterDiameterMetric / 2.0;
const zOffset = probeBlockHeight;
if (probeType === "z") {
ControllerMethods.send(`
G21
G92 Z0
G38.2 Z -25.4 F${fastSeek}
G91 G1 Z 1
G38.2 Z -2 F${slowSeek}
G92 Z ${zOffset}
G91 G0 Z 25
M2
`);
} else {
// After probing Z, we want to drop the bit down:
// Ideally, 12.7mm/0.5in
// And we don't want to be more than 90% down on the probe block
// Also, add zlift to compensate for the fact that we lift after probing Z
const plunge = Math.min(cutterLength, zOffset * 0.9) + zLift;
ControllerMethods.send(`
G21
G92 X0 Y0 Z0
G38.2 Z -25 F${fastSeek}
G91 G1 Z 1
G38.2 Z -2 F${slowSeek}
G92 Z ${zOffset}
G91 G0 Z ${zLift}
G91 G0 X 20
G91 G0 Z ${-plunge}
G38.2 X -20 F${fastSeek}
G91 G1 X 1
G38.2 X -2 F${slowSeek}
G92 X ${xOffset}
G91 G0 X 1
G91 G0 Y 20
G91 G0 X -20
G38.2 Y -20 F${fastSeek}
G91 G1 Y 1
G38.2 Y -2 F${slowSeek}
G92 Y ${yOffset}
G91 G0 Y 3
G91 G0 Z 25
M2
`);
}
}
</script>
<Dialog
bind:open
class="probe-dialog"
scrimClickAction=""
aria-labelledby="probe-dialog-title"
aria-describedby="probe-dialog-content"
surface$style="width: 700px; max-width: calc(100vw - 32px);"
>
<Title id="probe-dialog-title">Probing {probeType?.toUpperCase()}</Title>
<Content id="probe-dialog-content" style="overflow: visible;">
<div class="steps">
<p>
<b>Step {steps.indexOf(currentStep) + 1} of {steps.length}</b>
</p>
<ul>
{#each steps as step}
<li class:active={currentStep === step}>
{stepLabels[step]}
</li>
{/each}
</ul>
</div>
<div style="width: 100%">
{#if currentStep === "CheckProbe"}
<p>
Attach the probe magnet to the collet, then touch the probe
block to the bit.
</p>
<Icon
data={probeType === "xyz" ? CheckXYZ : CheckZ}
size="300px"
class="probe-icon-svg"
/>
{:else if currentStep === "BitDimensions"}
<TextFieldWithOptions
label="Cutter diameter"
variant="filled"
spellcheck="false"
style="width: 100%;"
bind:value={cutterDiameterString}
options={[imperialBits, metricBits]}
valid={isFinite(cutterDiameterMetric)}
helperText={`Examples: 1/2", 10 mm, 0.25 in`}
/>
<Icon data={BitDiameter} size="150px" class="probe-icon-svg" />
{:else if currentStep === "PlaceProbeBlock"}
<p>
{#if probeType === "xyz"}
Place the probe block face up, on the lower-left corner
of your workpiece.
{:else}
Place the probe block face down, with the bit above the
recess.
{/if}
</p>
<Icon
data={probeType === "xyz" ? PlaceXYZ : PlaceZ}
width="304px"
height="129px"
class="probe-icon-svg"
/>
<p>
The probing procedure will begin as soon as you click
'Next'.
</p>
{:else if currentStep === "Probe"}
<p>Probing in progress...</p>
<LinearProgress indeterminate />
{:else if currentStep === "Done"}
{#if $probingFailed}
<h3>Emergency Stop!</h3>
<p>Could not find the probe block during probing!</p>
<p>
Make sure the tip of the bit is less than {metric
? "25mm"
: "1 in"}
above the probe block, and try again.
</p>
{:else}
<p>Don't forget to put away the probe!</p>
<Icon
data={probeType === "xyz" ? PutAwayXYZ : PutAwayZ}
width="329px"
height="256px"
class="probe-icon-svg"
/>
{#if probeType === "xyz"}
<p>The machine will now move to the XY origin.</p>
<p>Watch your hands!</p>
{/if}
{/if}
{/if}
</div>
</Content>
<Actions>
{#if showCancelButton}
<Button on:click={() => ($cancelled = true)}>
<Label>Cancel</Label>
</Button>
{/if}
<Button
defaultAction
data-mdc-dialog-action={nextButton.allowClose ? "close" : ""}
disabled={nextButton.disabled}
on:click={() => ($userAcknowledged = true)}
>
<Label>
{nextButton.label}
</Label>
</Button>
</Actions>
</Dialog>
<style lang="scss">
$primary: #0078e7;
$very-dark: #555;
$text: #777;
$grey: #bbb;
$light: #ddd;
:global {
.probe-dialog {
.mdc-linear-progress {
height: 10px;
margin: 10px 0;
}
.mdc-linear-progress__bar-inner {
border-top-width: 10px;
}
.probe-icon-svg {
display: block;
margin: 20px auto;
}
#probe-dialog-content {
display: flex;
flex-direction: row;
}
.bit-dimensions {
display: flex;
flex-direction: column;
}
.steps {
margin-right: 50px;
ul {
margin: 0 auto;
list-style-type: none;
counter-reset: steps;
margin: 0;
font-family: sans-serif;
padding-inline-start: 20px;
}
ul li {
padding: 0 0 12px 30px;
position: relative;
margin: 0;
white-space: nowrap;
color: $text;
&:after {
position: absolute;
top: 3px;
left: 0.5px;
content: "";
border: 2px solid $text;
border-radius: 50%;
display: inline-block;
height: 11px;
width: 11px;
text-align: center;
line-height: 12px;
background: transparent;
}
&:before {
position: absolute;
left: 7px;
top: 22px;
bottom: 0;
content: "";
width: 0;
border-left: 2px solid $text;
}
&:last-of-type:before {
border: none;
}
&.active {
color: $primary;
font-weight: bold;
&:after {
border: 3px solid $primary;
top: 2.5px;
left: 0;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,67 @@
<script lang="ts">
import * as api from "$lib/api";
import TextField from "@smui/textfield";
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import Button, { Label } from "@smui/button";
import { virtualKeyboardChange } from "$lib/CustomActions";
export let open;
let code = "";
async function onContinue() {
const url = `remote-diagnostics?command=connect&code=${code}`;
const result = await api.GET(url);
if (result.code === 401) {
alert("The 6-digit code you provided was incorrect");
} else {
alert("Success!");
}
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="remote-diagnostics-dialog-title"
aria-describedby="remote-diagnostics-dialog-content"
>
<Title id="remote-diagnostics-dialog-title">Remote Diagnostics</Title>
<Content id="remote-diagnostics-dialog-content">
<p>
This feature enables remote diagnosis of customer issues. It
requires a 6-digit code that is provided by Onefinity support during
a live support session.
</p>
<TextField
bind:value={code}
label="6-digit code"
type="number"
variant="filled"
invalid={code?.length !== 6}
use={[InitialFocus, virtualKeyboardChange((v) => (code = v))]}
/>
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button
defaultAction
on:click={onContinue}
disabled={code?.length !== 6}
>
<Label>Continue</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,81 @@
<script type="ts">
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import Button, { Label } from "@smui/button";
import Radio from "@smui/radio";
import FormField from "@smui/form-field";
import MessageDialog from "$dialogs/MessageDialog.svelte";
import * as Api from "$lib/api";
import { onMount } from "svelte";
const options = [
{ value: 0, label: "Normal" },
{ value: 1, label: "Upside-down" },
];
export let open;
let currentValue;
let value;
let rebooting;
onMount(async () => {
const result = await Api.GET("screen-rotation");
currentValue = value = result.rotated ? 1 : 0;
});
async function onConfirm() {
rebooting = true;
await Api.PUT("screen-rotation", { rotated: value === 1 });
}
</script>
<MessageDialog open={rebooting} title="Rebooting" noaction>
Rebooting to apply the new screen rotation...
</MessageDialog>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="screen-rotation-dialog-title"
aria-describedby="screen-rotation-dialog-content"
>
<Title id="screen-rotation-dialog-title">Screen Rotation</Title>
<Content id="screen-rotation-dialog-content">
{#each options as option}
<FormField>
<Radio bind:group={value} value={option.value} />
<span slot="label">
{option.label}
</span>
</FormField>
{/each}
</Content>
<Actions>
<Button use={[InitialFocus]}>
<Label>Cancel</Label>
</Button>
<Button
defaultAction
disabled={value === currentValue}
on:click={onConfirm}
>
<Label>Confirm & Reboot</Label>
</Button>
</Actions>
</Dialog>
<style lang="scss">
:global {
#screen-rotation-dialog-content {
display: flex;
flex-direction: column;
}
}
</style>

View File

@@ -0,0 +1,72 @@
<script lang="ts">
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import TextField from "@smui/textfield";
import Button, { Label } from "@smui/button";
import { ControllerMethods } from "$lib/RegisterControllerMethods";
import { virtualKeyboardChange } from "$lib/CustomActions";
export let open: boolean;
export let axis = "";
let value = 0;
let homed = false;
let wasOpen = false;
$: if (open != wasOpen) {
if (open) {
homed = ControllerMethods.isAxisHomed(axis);
}
wasOpen = open;
}
function onUnhome() {
ControllerMethods.unhome(axis);
}
function onConfirm() {
ControllerMethods.set_position(axis, value);
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="set-axis-position-dialog-title"
aria-describedby="set-axis-position-dialog-content"
>
<Title id="set-axis-position-dialog-title">
Set {axis.toUpperCase()} Axis Position
</Title>
<Content id="set-axis-position-dialog-content">
<TextField
label="Position"
type="number"
bind:value
use={[InitialFocus, virtualKeyboardChange((v) => (value = v))]}
spellcheck="false"
variant="filled"
style="width: 100%;"
/>
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
{#if homed}
<Button on:click={onUnhome}>
<Label>Unhome</Label>
</Button>
{/if}
<Button defaultAction on:click={onConfirm}>
<Label>Set</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,290 @@
<script lang="ts">
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import Button, { Label } from "@smui/button";
import TextField from "@smui/textfield";
import Select, { Option } from "@smui/select";
import CircularProgress from "@smui/circular-progress";
import VirtualList from "svelte-tiny-virtual-list";
import * as api from "$lib/api";
import { virtualKeyboardChange } from "$lib/CustomActions";
const itemHeight = 35;
type Timezone = {
label: string;
value: string;
};
export let open = false;
let year = "";
let month = "";
let day = "";
let hour = "";
let minute = "";
let am = true;
let wasOpen = false;
let loading = true;
let timezones: Timezone[] = [];
let currentTimezoneIndex: number;
let selectedTimezoneIndex: number;
let networkTimeSynchronized: boolean;
$: if (open != wasOpen) {
if (!wasOpen) {
loadData();
}
wasOpen = open;
}
async function loadData() {
loading = true;
const result = await api.GET("time");
parseTimezones(result.timezones);
parseTimeinfo(result.timeinfo);
const date = new Date();
year = date.getFullYear().toString();
month = (date.getMonth() + 1).toString();
day = date.getDate().toString();
hour = date.getHours().toString();
minute = date.getMinutes().toString();
am = date.getHours() >= 12;
loading = false;
}
function parseTimeinfo(str: string) {
const matches = Array.from(str.matchAll(/\s*([^:]+):\s+(.+)/gm));
let currentTimezoneValue;
for (const match of matches) {
let [, label, value] = match;
switch (label) {
case "Time zone":
currentTimezoneValue = value.split(" ")[0];
break;
case "NTP synchronized":
networkTimeSynchronized = false;
break;
}
}
currentTimezoneIndex = timezones.findIndex(
(tz) => tz.value === currentTimezoneValue
);
selectedTimezoneIndex = currentTimezoneIndex;
}
function parseTimezones(str: string) {
const matches = Array.from(str.matchAll(/\s*(\S+)\s*/gm));
timezones = [];
for (let [, value] of matches) {
timezones.push({
label: value.replace(/_/g, " "),
value,
});
}
// Sort alphabetically, but with the current timezone at the top of the list
timezones.sort((a, b) => {
switch (true) {
case a.value === "UTC":
return -1;
case b.value === "UTC":
return 1;
default:
return a.value.localeCompare(b.value);
}
});
}
async function onConfirm() {
const YY = year.toString().padStart(2, "0");
const MM = month.toString().padStart(2, "0");
const DD = day.toString().padStart(2, "0");
let hh = hour.toString().padStart(2, "0");
const mm = minute.toString().padStart(2, "0");
if (Number(hour) < 12 && !am) {
hh = (hour + 12).toString().padStart(2, "0");
}
await api.PUT("time", {
datetime: `${YY}-${MM}-${DD} ${hh}:${mm}:00`,
timezone: timezones[selectedTimezoneIndex].value,
});
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="set-time-dialog-title"
aria-describedby="set-time-dialog-content"
>
<Title id="set-time-dialog-title">Change Time & Timezone</Title>
<Content id="set-time-dialog-content">
{#if loading}
<div style="display: flex; justify-content: center">
<CircularProgress
style="height: 32px; width: 32px;"
indeterminate
/>
</div>
{:else}
{#if networkTimeSynchronized}
<p>
Because this controller is connected to the internet, the
time is managed automatically, and cannot be manually set.
</p>
{:else}
<p>
Any time the controller is turned off, the time will need to
be reset. If you connect the controller to the internet, the
time will be managed automatically.
</p>
<TextField
bind:value={year}
use={[
InitialFocus,
virtualKeyboardChange((v) => (year = v)),
]}
label="Year"
type="number"
variant="filled"
style="width: 70px;"
/>
<TextField
bind:value={month}
use={[virtualKeyboardChange((v) => (month = v))]}
label="Month"
type="number"
variant="filled"
style="width: 70px;"
/>
<TextField
bind:value={day}
use={[virtualKeyboardChange((v) => (day = v))]}
label="Day"
type="number"
variant="filled"
style="width: 70px;"
/>
<span style="display: inline-block; width: 20px;" />
<TextField
bind:value={hour}
use={[virtualKeyboardChange((v) => (hour = v))]}
label="Hour"
type="number"
variant="filled"
style="width: 70px;"
/>
<TextField
bind:value={minute}
use={[virtualKeyboardChange((v) => (minute = v))]}
label="Minute"
type="number"
variant="filled"
style="width: 70px;"
/>
<Select
label=""
bind:value={am}
style="width: 90px;"
variant="filled"
>
<Option value={true}>AM</Option>
<Option value={false}>PM</Option>
</Select>
{/if}
<div class="timezones-container" style="margin-top: 30px;">
<VirtualList
width="100%"
height={itemHeight * 6}
itemCount={timezones.length}
itemSize={itemHeight}
scrollToIndex={currentTimezoneIndex}
scrollToAlignment="center"
>
<div
slot="item"
let:index
let:style
{style}
class="timezone"
class:selected={index === selectedTimezoneIndex}
on:click={() => (selectedTimezoneIndex = index)}
>
{timezones[index].label}
</div>
</VirtualList>
</div>
{/if}
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button
defaultAction
disabled={selectedTimezoneIndex === -1}
on:click={onConfirm}
>
<Label>Confirm</Label>
</Button>
</Actions>
</Dialog>
<style lang="scss">
@use "sass:color";
$primary: #0078e7;
$very-dark: #555;
$text: #777;
$grey: #bbb;
$light: #ddd;
.timezones-container {
:global {
.virtual-list-wrapper {
border: 1px solid #ccc;
border-radius: 3px;
overflow-x: hidden;
overflow-y: scroll;
}
}
.timezone {
font-size: 14px;
display: flex;
align-items: center;
margin: 0;
padding-left: 10px;
&.selected {
color: $primary;
background-color: color.adjust($primary, $lightness: 50%);
font-weight: bold;
}
}
}
</style>

View File

@@ -0,0 +1,38 @@
<script lang="ts">
import Dialog, { Title, Actions, InitialFocus } from "@smui/dialog";
import Button, { Label } from "@smui/button";
import * as Api from "$lib/api";
export let open;
function shutdown() {
Api.PUT("shutdown");
}
function restart() {
Api.PUT("reboot");
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="shutdown-dialog-title"
aria-describedby="shutdown-dialog-content"
>
<Title id="shutdown-dialog-title">Confirm Shutdown?</Title>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button on:click={shutdown}>
<Label>Shutdown</Label>
</Button>
<Button use={[InitialFocus]} on:click={restart}>
<Label>Restart</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,83 @@
<script lang="ts">
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import Button, { Label } from "@smui/button";
import LinearProgress from "@smui/linear-progress";
export let open = false;
export let file: File;
export let onComplete: () => void;
let wasOpen = false;
let xhr;
let progress;
$: if (open != wasOpen) {
if (!wasOpen) {
beginUpload();
}
wasOpen = open;
}
$: if (!open) {
xhr = undefined;
}
async function beginUpload() {
progress = 0;
xhr = new XMLHttpRequest();
xhr.upload.onload = () => {
open = false;
if (onComplete) {
onComplete();
}
};
xhr.upload.onerror = () => {
open = false;
alert("Upload failed.");
};
xhr.upload.onabort = () => {
open = false;
};
xhr.upload.onprogress = (event) => {
progress = event.loaded / event.total;
};
xhr.open("PUT", `/api/file/${encodeURIComponent(file.name)}`);
xhr.send(file);
}
function onCancel() {
xhr.abort();
}
</script>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="upload-dialog-title"
aria-describedby="upload-dialog-content"
>
<Title id="upload-dialog-title">
Uploading {#if file}{file.name}...{/if}
</Title>
<Content id="upload-dialog-content">
<LinearProgress {progress} />
</Content>
<Actions>
<Button on:click={onCancel} use={[InitialFocus]}>
<Label>Cancel</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,112 @@
<script lang="ts">
import Dialog, {
Title,
Content,
Actions,
InitialFocus,
} from "@smui/dialog";
import Button, { Label } from "@smui/button";
import TextField from "@smui/textfield";
import Icon from "@smui/textfield/icon";
import HelperText from "@smui/textfield/helper-text";
import MessageDialog from "$dialogs/MessageDialog.svelte";
import type { WifiNetwork } from "$lib/NetworkInfo";
import * as api from "$lib/api";
import { virtualKeyboardChange } from "$lib/CustomActions";
export let open = false;
export let network: WifiNetwork;
let rebooting = false;
let password = "";
let showPassword = false;
$: needPassword = !network?.active && network?.Encryption !== "Open";
$: connectOrDisconnect = network?.active ? "Disconnect" : "Connect";
$: connectToOrDisconnectFrom = network?.active
? "Disconnect from"
: "Connect to";
$: if (open) {
password = "";
}
async function onConfirm() {
rebooting = true;
await api.PUT("network", {
wifi: {
enabled: !network.active,
ssid: network.Name,
password,
},
});
}
</script>
<MessageDialog open={rebooting} title="Rebooting" noaction>
Rebooting to apply Wifi changes...
</MessageDialog>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="wifi-connection-dialog-title"
aria-describedby="wifi-connection-dialog-content"
>
<Title id="wifi-connection-dialog-title">
{connectToOrDisconnectFrom}
{network.Name}
</Title>
<Content id="wifi-connection-dialog-content">
{#if needPassword}
<TextField
bind:value={password}
use={[
InitialFocus,
virtualKeyboardChange((v) => (password = v)),
]}
label="Password"
spellcheck="false"
variant="filled"
type={showPassword ? "text" : "password"}
style="width: 100%;"
>
<div
slot="trailingIcon"
on:click={() => (showPassword = !showPassword)}
>
<Icon
class={`fa ${showPassword ? "fa-eye-slash" : "fa-eye"}`}
/>
</div>
<HelperText persistent slot="helper">
Wifi passwords must be 8 to 128 characters
</HelperText>
</TextField>
{/if}
<p>
<em>
Clicking {connectOrDisconnect} will reboot the controller to apply
the changes.
</em>
</p>
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button
defaultAction
on:click={onConfirm}
disabled={needPassword &&
(password.length < 8 || password.length > 128)}
>
<Label>{connectOrDisconnect} & Reboot</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -0,0 +1,14 @@
import { writable } from "svelte/store";
type DisplayUnits = "METRIC" | "IMPERIAL";
export const Config = writable<Record<string, any>>({});
export const DisplayUnits = writable<DisplayUnits>();
export function handleConfigUpdate(config: Record<string, any>) {
Config.set(config);
}
export function setDisplayUnits(value: DisplayUnits) {
DisplayUnits.set(value);
}

Some files were not shown because too many files have changed in this diff Show More