diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..3d185e2 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,15 @@ +# 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} + +RUN apt update \ + && apt upgrade -y \ + && apt install -y \ + build-essential git wget binfmt-support qemu gcc-9 \ + parted gcc-avr avr-libc avrdude python3 python3-pip python3-tornado \ + curl unzip python3-setuptools gcc-arm-linux-gnueabihf bc vim sudo \ + && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 9 \ + && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ + && apt install -y nodejs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..62b154a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +// 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": ["--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" ], + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + // "remoteUser": "vscode", + + "features": { + "sshd": "latest" + } +} diff --git a/docs/development.md b/docs/development.md index 6a82eb5..028d1b5 100644 --- a/docs/development.md +++ b/docs/development.md @@ -8,12 +8,18 @@ Debian Linux are not supported. On a Debian Linux (9.6.0 stable) system install the required packages: - sudo apt-get update - sudo apt-get install -y build-essential git wget binfmt-support qemu \ - parted gcc-avr avr-libc avrdude pylint3 python3 python3-tornado curl \ - unzip python3-setuptools gcc-arm-linux-gnueabihf bc sudo - curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash - - sudo apt-get install -y nodejs + apt update + apt upgrade -y + apt install -y \ + build-essential git wget binfmt-support qemu gcc-9 \ + parted gcc-avr avr-libc avrdude python3 python3-tornado curl \ + unzip python3-setuptools gcc-arm-linux-gnueabihf bc sudo + + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 9 + + curl -fsSL https://deb.nodesource.com/setup_18.x | bash - + + apt install -y nodejs ## Getting the Source Code diff --git a/package-lock.json b/package-lock.json index 8bccad7..a4779f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bbctrl", - "version": "1.0.10b7", + "version": "1.0.10b10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bbctrl", - "version": "1.0.10b7", + "version": "1.0.10b10", "hasInstallScript": true, "license": "GPL-3.0+", "dependencies": { diff --git a/package.json b/package.json index f2f463a..edc4d21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bbctrl", - "version": "1.0.10b7", + "version": "1.0.10b10", "homepage": "https://onefinitycnc.com/", "repository": "https://github.com/OneFinityCNC/onefinity", "license": "GPL-3.0+", diff --git a/scripts/rpi-chroot.sh b/scripts/rpi-chroot.sh index 35e15be..ef52ca6 100755 --- a/scripts/rpi-chroot.sh +++ b/scripts/rpi-chroot.sh @@ -1,7 +1,6 @@ #!/bin/bash -ex ROOT="$PWD/rpi-root" -LOOP=12 if [ $# -lt 1 ]; then echo "Usage: $0 " @@ -9,7 +8,8 @@ if [ $# -lt 1 ]; then fi IMAGE="$1" -LOOP_DEV=/dev/loop${LOOP} +LOOP_BOOT= +LOOP_ROOT= EXEC= if [ $# -gt 1 ]; then @@ -26,25 +26,28 @@ fi # Clean up on EXIT function cleanup { umount "$ROOT"/{dev/pts,dev,sys,proc,boot,mnt/host,} 2>/dev/null || true - losetup -d $LOOP_DEV 2>/dev/null || true + losetup -d $LOOP_BOOT 2>/dev/null || true + losetup -d $LOOP_ROOT 2>/dev/null || true rmdir "$ROOT" 2>/dev/null || true } trap cleanup EXIT -# set up image as loop device -losetup $LOOP_DEV "$IMAGE" -partprobe $LOOP_DEV +LOOP_BOOT=`losetup -f` +losetup -o 4194304 $LOOP_BOOT "$IMAGE" + +LOOP_ROOT=`losetup -f` +losetup -o 48234496 $LOOP_ROOT "$IMAGE" # check and fix filesystems -fsck -f ${LOOP_DEV}p1 -fsck -f ${LOOP_DEV}p2 +fsck -f $LOOP_BOOT +fsck -f $LOOP_ROOT # make dir mkdir -p "$ROOT" # mount partition -mount -o rw ${LOOP_DEV}p2 -t ext4 "$ROOT" -mount -o rw ${LOOP_DEV}p1 "$ROOT/boot" +mount -o rw $LOOP_ROOT -t ext4 "$ROOT" +mount -o rw $LOOP_BOOT "$ROOT/boot" # mount binds mount --bind /dev "$ROOT/dev/" diff --git a/src/bbserial/Makefile b/src/bbserial/Makefile index 0b7bbc6..792ddfd 100644 --- a/src/bbserial/Makefile +++ b/src/bbserial/Makefile @@ -5,7 +5,7 @@ ccflags-y:=-std=gnu99 -Wno-declaration-after-statement KPKG=raspberrypi-kernel_1.20171029-1.tar.gz KURL=https://github.com/dbrgn/linux-rpi/archive/$(KPKG) -KDIR=linux-rpi-raspberrypi-kernel_1.20171029-1 +KDIR=/tmp/rpi-kernel export KERNEL=kernel7 KOPTS=ARCH=arm CROSS_COMPILE=$(CROSS) -C $(KDIR) @@ -14,7 +14,9 @@ all: $(KDIR) $(MAKE) $(KOPTS) M=$(DIR) modules $(KDIR): $(KPKG) - tar xf $(KPKG) + rm -rf $(KDIR) + mkdir -p $(KDIR) + tar xf $(KPKG) -C $(KDIR) --strip-components 1 $(MAKE) $(KOPTS) bcm2709_defconfig $(MAKE) $(KOPTS) modules_prepare diff --git a/src/js/admin-general-view.js b/src/js/admin-general-view.js index 795bd1b..02aac51 100644 --- a/src/js/admin-general-view.js +++ b/src/js/admin-general-view.js @@ -46,9 +46,7 @@ module.exports = { data: function () { return { - configRestored: false, confirmReset: false, - configReset: false, autoCheckUpgrade: true, reset_variant: '' } @@ -71,29 +69,31 @@ module.exports = { }, restore: function (e) { - var files = e.target.files || e.dataTransfer.files; - if (!files.length) return; + const files = e.target.files || e.dataTransfer.files; + if (!files.length) { + return; + } - var fr = new FileReader(); - fr.onload = function (e) { - var config; + const fileReader = new FileReader(); + fileReader.onload = async ({ target }) => { + let config; try { - config = JSON.parse(e.target.result); + config = JSON.parse(target.result); } catch (ex) { api.alert("Invalid config file"); return; } - api.put('config/save', config).done(function (data) { + try { + await api.put('config/save', config); this.$dispatch('update'); - this.configRestored = true; - - }.bind(this)).fail(function (error) { + SvelteComponents.showDialog("Message", { title: "Success", message: "Configuration restored" }) + } catch (error) { api.alert('Restore failed', error); - }) - }.bind(this); + } + } - fr.readAsText(files[0]); + fileReader.readAsText(files[0]); }, reset: async function () { @@ -107,7 +107,7 @@ module.exports = { await api.put('config/save', config) this.confirmReset = false; this.$dispatch('update'); - this.configRestored = true; + SvelteComponents.showDialog("Message", { title: "Success", message: "Configuration restored" }) } catch (err) { api.alert('Restore failed'); console.error('Restore failed', err); diff --git a/src/js/app.js b/src/js/app.js index 46a07ff..7210983 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -106,8 +106,7 @@ module.exports = new Vue({ firmwareUpgrading: false, checkedUpgrade: false, firmwareName: "", - latestVersion: "", - confirmShutdown: false, + latestVersion: "" }; }, @@ -283,10 +282,17 @@ module.exports = new Vue({ }, show_upgrade: function () { - if (!this.latestVersion) return false; + if (!this.latestVersion) { + return false; + } + return is_newer_version(this.config.version, this.latestVersion); }, + showShutdownDialog: function () { + SvelteComponents.showDialog("Shutdown"); + }, + update: async function () { const config = await api.get("config/load"); @@ -303,16 +309,6 @@ module.exports = new Vue({ SvelteComponents.handleConfigUpdate(this.config); }, - shutdown: function () { - this.confirmShutdown = false; - api.put("shutdown"); - }, - - reboot: function () { - this.confirmShutdown = false; - api.put("reboot"); - }, - connect: function () { this.sock = new Sock(`//${location.host}/sockjs`); diff --git a/src/js/control-view.js b/src/js/control-view.js index 47bf907..57f60ac 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -39,17 +39,7 @@ module.exports = { jog_adjust: parseInt(cookie.get('jog-adjust', 2)), deleteGCode: false, tab: 'auto', - toolpath_msg: { - x: false, - y: false, - z: false, - a: false, - b: false, - c: false - }, ask_home: true, - ask_zero_xy_msg: false, - ask_zero_z_msg: false, showGcodeMessage: false } }, @@ -244,7 +234,6 @@ module.exports = { SvelteComponents.registerControllerMethods({ stop: (...args) => this.stop(...args), send: (...args) => this.send(...args), - goto_zero: (...args) => this.goto_zero(...args), isAxisHomed: (axis) => this[axis].homed, unhome: (...args) => this.unhome(...args), set_position: (...args) => this.set_position(...args), @@ -253,18 +242,6 @@ module.exports = { }, methods: { - goto_zero(zero_x, zero_y, zero_z, zero_a) { - const xcmd = zero_x ? "X0" : ""; - const ycmd = zero_y ? "Y0" : ""; - const zcmd = zero_z ? "Z0" : ""; - const acmd = zero_a ? "A0" : ""; - - this.ask_zero_xy_msg = false; - this.ask_zero_z_msg = false; - - this.send('G90\nG0' + xcmd + ycmd + zcmd + acmd + '\n'); - }, - getJogIncrStyle(value) { const weight = `font-weight:${this.jog_incr === value ? 'bold' : 'normal'}`; const color = this.jog_incr === value ? "color:#0078e7" : ""; @@ -436,8 +413,12 @@ module.exports = { SvelteComponents.showDialog("SetAxisPosition", { axis }); }, - show_toolpath_msg: function (axis) { - this.toolpath_msg[axis] = true; + showMoveToZeroDialog: function (axes) { + SvelteComponents.showDialog("MoveToZero", { axes }); + }, + + showToolpathMessageDialog: function (axis) { + SvelteComponents.showDialog("Message", { title: this[axis].toolmsg }); }, set_position: function (axis, position) { diff --git a/src/js/main.js b/src/js/main.js index 9d93e45..12fd4c1 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -39,7 +39,6 @@ function uuid(length) { return s } - $(function () { if (typeof cookie_get('client-id') == 'undefined') { cookie_set('client-id', uuid(), 10000); diff --git a/src/pug/index.pug b/src/pug/index.pug index 3d65e50..89eb5a2 100644 --- a/src/pug/index.pug +++ b/src/pug/index.pug @@ -65,16 +65,8 @@ html(lang="en") li.pure-menu-heading a.pure-menu-link(href="#help") Help - button.pure-button.pure-button-primary(@click="confirmShutdown = true", style="width: 100%") - .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 - + button.pure-button.pure-button-primary(@click="showShutdownDialog", style="width: 100%") + .fa.fa-power-off #main .header diff --git a/src/pug/templates/admin-general-view.pug b/src/pug/templates/admin-general-view.pug index d90a20c..8eb358a 100644 --- a/src/pug/templates/admin-general-view.pug +++ b/src/pug/templates/admin-general-view.pug @@ -45,9 +45,6 @@ script#admin-general-view-template(type="text/x-template") label.pure-button.pure-button-primary(@click="restore_config") Restore form.restore-config.file-upload 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 message(:show.sync="confirmReset") @@ -70,10 +67,6 @@ script#admin-general-view-template(type="text/x-template") button.pure-button(@click="confirmReset = false") Cancel button.pure-button.pure-button-primary(@click="reset") Reset - message(:show.sync="configReset") - h3(slot="header") Success - p(slot="body") Configuration reset. - h2 Debugging a(href="/api/log", target="_blank") button.pure-button.pure-button-primary View Log diff --git a/src/pug/templates/control-view.pug b/src/pug/templates/control-view.pug index 83845b9..6f5887d 100644 --- a/src/pug/templates/control-view.pug +++ b/src/pug/templates/control-view.pug @@ -10,32 +10,6 @@ script#control-view-template(type="text/x-template") div(slot="footer") label Simulating {{(toolpath_progress || 0) | percent}} - 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 - table(style="table-layout: fixed; width: 100%;") tr(style="height: fit-content;") td(style="white-space: nowrap; width: 410px;", rowspan="2") @@ -60,12 +34,12 @@ script#control-view-template(type="text/x-template") td(style="height:100px",align="center") button(@click="jog_fn(-1,0,0,0)") X- td(style="height:100px",align="center") - button(@click="ask_zero_xy_msg = true") + button(@click="showMoveToZeroDialog('xy')") .fa.fa-bullseye(style="font-size: 173%") td(style="height:100px",align="center") button(@click="jog_fn(1,0,0,0)") X+ td(style="height:100px",align="center") - button(@click='ask_zero_z_msg = true') Z0 + button(@click="showMoveToZeroDialog('z')") Z0 tr td(style="height:100px",align="center") button(@click="jog_fn(-1,-1,0,0)") @@ -133,20 +107,10 @@ script#control-view-template(type="text/x-template") td.state .fa(:class=`'fa-' + ${axis}.icon`) | {{#{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`) | {{#{axis}.tstate}} - - 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 button.pure-button(:disabled="!can_set_axis", title=`Set {{'${axis}' | upper}} axis position.`, diff --git a/src/pug/templates/settings-view.pug b/src/pug/templates/settings-view.pug index 8a0061f..4b6cf85 100644 --- a/src/pug/templates/settings-view.pug +++ b/src/pug/templates/settings-view.pug @@ -1,2 +1,2 @@ script#settings-view-template(type="text/x-template") - #settings \ No newline at end of file + #settings diff --git a/src/py/bbctrl/FileHandler.py b/src/py/bbctrl/FileHandler.py index 33d5eb3..7f2195c 100644 --- a/src/py/bbctrl/FileHandler.py +++ b/src/py/bbctrl/FileHandler.py @@ -5,7 +5,7 @@ import glob import tornado from tornado import gen from tornado.web import HTTPError - +from tornado.escape import url_unescape; def safe_remove(path): try: @@ -20,7 +20,8 @@ class FileHandler(bbctrl.APIHandler): if self.request.method == 'PUT': self.request.connection.set_max_body_size(2 ** 30) - self.uploadFilename = self.request.path.split('/')[-1] \ + filename = self.request.path.split('/')[-1] + self.uploadFilename = url_unescape(filename) \ .replace('\\', '/') \ .replace('#', '-') \ .replace('?', '-') diff --git a/src/py/bbctrl/Jog.py b/src/py/bbctrl/Jog.py index f198bdf..39c1537 100644 --- a/src/py/bbctrl/Jog.py +++ b/src/py/bbctrl/Jog.py @@ -37,7 +37,7 @@ class Jog(inevent.JogHandler): config = { "Logitech Logitech RumblePad 2 USB": { - "deadband": 0.1, + "deadband": 0.15, "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z], "dir": [1, -1, -1, 1], "arrows": [ABS_HAT0X, ABS_HAT0Y], @@ -46,7 +46,7 @@ class Jog(inevent.JogHandler): }, "default": { - "deadband": 0.1, + "deadband": 0.15, "axes": [ABS_X, ABS_Y, ABS_RY, ABS_RX], "dir": [1, -1, -1, 1], "arrows": [ABS_HAT0X, ABS_HAT0Y], diff --git a/src/py/bbctrl/State.py b/src/py/bbctrl/State.py index 89badcc..03e8a24 100644 --- a/src/py/bbctrl/State.py +++ b/src/py/bbctrl/State.py @@ -4,9 +4,10 @@ import json import uuid import os import socket -import bbctrl import iw_parse -from tornado import gen +import threading +import subprocess +import time from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler @@ -20,6 +21,7 @@ def call_get_output(cmd): class UploadChangeHandler(FileSystemEventHandler): + def __init__(self, state): self.state = state @@ -28,7 +30,9 @@ class UploadChangeHandler(FileSystemEventHandler): class State(object): + def __init__(self, ctrl): + self.lock = threading.Lock() self.ctrl = ctrl self.log = ctrl.log.get('State') @@ -72,39 +76,40 @@ class State(object): self.load_files() observer = Observer() - observer.schedule(UploadChangeHandler( - self), self.ctrl.get_upload(), recursive=True) + observer.schedule(UploadChangeHandler(self), + self.ctrl.get_upload(), + recursive=True) observer.start() - self._updateNetworkInfo() + threading.Thread(target=self._updateNetworkInfo, daemon=True).start() - @gen.coroutine def _updateNetworkInfo(self): - try: - ipAddresses = call_get_output(['hostname', '-I']).split() - except: - ipAddresses = "" + while True: + try: + ipAddresses = call_get_output(['hostname', '-I']).split() + except: + ipAddresses = "" - hostname = socket.gethostname() + hostname = socket.gethostname() - try: - wifi = json.loads(call_get_output(['config-wifi', '-j'])) - except: - wifi = {'enabled': False} + 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'] = [] + try: + lines = iw_parse.call_iwlist().decode("utf-8").split("\n") + wifi['networks'] = iw_parse.get_parsed_cells(lines) + except: + wifi['networks'] = [] - self.set('networkInfo', { - 'ipAddresses': ipAddresses, - 'hostname': hostname, - 'wifi': wifi - }) + self.set('networkInfo', { + 'ipAddresses': ipAddresses, + 'hostname': hostname, + 'wifi': wifi + }) - self.timeout = self.ctrl.ioloop.call_later(5, self._updateNetworkInfo) + time.sleep(5) def reset(self): # Unhome all motors @@ -217,15 +222,20 @@ class State(object): self.callbacks[self.resolve(name)] = cb def set(self, name, value): - name = self.resolve(name) + self.lock.acquire() + try: + name = self.resolve(name) - if not name in self.vars or self.vars[name] != value: - self.vars[name] = value - self.changes[name] = value + if not name in self.vars or self.vars[name] != value: + self.vars[name] = value + self.changes[name] = value - # Trigger listener notify - if self.timeout is None: - self.timeout = self.ctrl.ioloop.call_later(0.25, self._notify) + # Trigger listener notify + if self.timeout is None: + self.timeout = self.ctrl.ioloop.call_later( + 0.25, self._notify) + finally: + self.lock.release() def update(self, update): for name, value in update.items(): @@ -377,7 +387,7 @@ class State(object): softMax = int(self.get(axis + '_tm', 0)) if softMax <= softMin + 1: return 'max-soft-limit must be at least 1mm greater ' \ - 'than min-soft-limit' + 'than min-soft-limit' def motor_enabled(self, motor): return bool(int(self.vars.get('%dme' % motor, 0))) diff --git a/src/py/inevent/JogHandler.py b/src/py/inevent/JogHandler.py index f03c5a8..2deac48 100644 --- a/src/py/inevent/JogHandler.py +++ b/src/py/inevent/JogHandler.py @@ -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 . # -# # -# The software is distributed in the hope that it will be useful, but # -# WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # -# Lesser General Public License for more details. # -# # -# You should have received a copy of the GNU Lesser General Public # -# License along with the software. If not, see # -# . # -# # -# For information regarding this software email: # -# "Joseph Coffland" # -# # -################################################################################ - import logging from inevent.Constants import * @@ -97,7 +70,31 @@ class JogHandler: # Process event if event.type == EV_ABS and event.code in config['axes']: - pass + old_axes = list(self.axes) + deadband = config['deadband'] + axis = config['axes'].index(event.code) + + self.axes[axis] = event.stream.state.abs[event.code] + self.axes[axis] *= config['dir'][axis] + + value = abs(self.axes[axis]) + if value >= deadband: + sign = -1 if self.axes[axis] < 0 else 1 + delta = value - deadband + range = 1 - deadband + + # Scale the new value to the available range (full range, minus the deadband) + self.axes[axis] = (delta * sign) / range + else: + self.axes[axis] = 0 + + if self.horizontal_lock and axis not in [0, 3]: + self.axes[axis] = 0 + + if self.vertical_lock and axis not in [1, 2]: + self.axes[axis] = 0 + + if old_axes[axis] != self.axes[axis]: changed = True elif event.type == EV_KEY and event.code in config['speed']: old_speed = self.speed @@ -115,22 +112,4 @@ class JogHandler: log.debug(event_to_string(event, state)) - # Update axes - old_axes = list(self.axes) - - for axis in range(4): - self.axes[axis] = event.stream.state.abs[config['axes'][axis]] - self.axes[axis] *= config['dir'][axis] - - if abs(self.axes[axis]) < config['deadband']: - self.axes[axis] = 0 - - if self.horizontal_lock and axis not in [0, 3]: - self.axes[axis] = 0 - - if self.vertical_lock and axis not in [1, 2]: - self.axes[axis] = 0 - - if old_axes != self.axes: changed = True - if changed: self.changed() diff --git a/src/static/js/three.min.js b/src/static/js/three.min.js index 75e5c31..a0a33c8 100644 --- a/src/static/js/three.min.js +++ b/src/static/js/three.min.js @@ -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? 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.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= [];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: "+ diff --git a/src/static/js/vue.js b/src/static/js/vue.js index c8d69ec..a357bde 100644 --- a/src/static/js/vue.js +++ b/src/static/js/vue.js @@ -394,7 +394,7 @@ var inBrowser = typeof window !== 'undefined' && Object.prototype.toString.call(window) !== '[object Object]'; // detect devtools - var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + var devtools = false; // UA sniffing for working around browser-specific quirks var UA = inBrowser && window.navigator.userAgent.toLowerCase(); @@ -9677,14 +9677,5 @@ var template = Object.freeze({ 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; - })); \ No newline at end of file diff --git a/src/svelte-components/src/components/DimensionInput.svelte b/src/svelte-components/src/components/DimensionInput.svelte deleted file mode 100644 index bec8141..0000000 --- a/src/svelte-components/src/components/DimensionInput.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - -
-
- menu.setOpen(true)} - /> - -
- - - {#each options as option} - onOptionSelected(option)}> - {option.label} - - {/each} - - -
- - \ No newline at end of file diff --git a/src/svelte-components/src/components/TextFieldWithOptions.svelte b/src/svelte-components/src/components/TextFieldWithOptions.svelte new file mode 100644 index 0000000..d1d487a --- /dev/null +++ b/src/svelte-components/src/components/TextFieldWithOptions.svelte @@ -0,0 +1,90 @@ + + +
+ showMenu(true)} + on:focusout={() => showMenu(false)} + use={[[virtualKeyboardChange, (newValue) => (value = newValue)]]} + {...$$restProps} + > +
+ {#if valid} + + {/if} +
+ {helperText} +
+ + +
+ {#each options as group} + + {#each group as option} + { + value = option; + showMenu(false); + + optionSelected = true; + }} + > + {option} + + {/each} + + {/each} +
+
+
+ + diff --git a/src/svelte-components/src/dialogs/ChangeHostnameDialog.svelte b/src/svelte-components/src/dialogs/ChangeHostnameDialog.svelte index 51bbdde..1cc79cb 100644 --- a/src/svelte-components/src/dialogs/ChangeHostnameDialog.svelte +++ b/src/svelte-components/src/dialogs/ChangeHostnameDialog.svelte @@ -4,6 +4,7 @@ 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 // @@ -57,7 +58,7 @@ } - + Rebooting to apply the hostname change... @@ -72,15 +73,12 @@ (hostname = newValue)]]} label="New Hostname" spellcheck="false" variant="filled" style="width: 100%;" /> - -

- Clicking Confirm will reboot the controller to apply the change. -

diff --git a/src/svelte-components/src/dialogs/DialogHost.svelte b/src/svelte-components/src/dialogs/DialogHost.svelte index a8765ec..0dadf2f 100644 --- a/src/svelte-components/src/dialogs/DialogHost.svelte +++ b/src/svelte-components/src/dialogs/DialogHost.svelte @@ -7,6 +7,9 @@ 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(); type HomeMachineDialogPropsType = { @@ -49,6 +52,25 @@ axis: string; }; + const MoveToZeroDialogProps = writable(); + type MoveToZeroDialogPropsType = { + open: boolean; + axes: "xy" | "z"; + }; + + const ShutdownDialogProps = writable(); + type ShutdownDialogPropsType = { + open: boolean; + }; + + const MessageDialogProps = writable(); + type MessageDialogPropsType = { + open: boolean; + title: string; + message: string; + noaction: boolean; + }; + export function showDialog( dialog: "HomeMachine", props: Omit @@ -84,6 +106,21 @@ props: Omit ); + export function showDialog( + dialog: "MoveToZero", + props: Omit + ); + + export function showDialog( + dialog: "Shutdown", + props: Omit + ); + + export function showDialog( + dialog: "Message", + props: Omit + ); + export function showDialog(dialog: string, props: any) { switch (dialog) { case "HomeMachine": @@ -114,12 +151,84 @@ 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}`); + throw new Error(`Unknown dialog '${dialog}'`); } } + + @@ -127,3 +236,6 @@ + + + diff --git a/src/svelte-components/src/dialogs/ManualHomeAxisDialog.svelte b/src/svelte-components/src/dialogs/ManualHomeAxisDialog.svelte index eb08d36..b008e83 100644 --- a/src/svelte-components/src/dialogs/ManualHomeAxisDialog.svelte +++ b/src/svelte-components/src/dialogs/ManualHomeAxisDialog.svelte @@ -3,6 +3,7 @@ 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 = ""; @@ -30,6 +31,7 @@ label="Absolute" type="number" bind:value + use={[[virtualKeyboardChange, (newValue) => (value = newValue)]]} variant="filled" style="width: 100%;" /> diff --git a/src/svelte-components/src/dialogs/MessageDialog.svelte b/src/svelte-components/src/dialogs/MessageDialog.svelte index 0cf3ea1..735341c 100644 --- a/src/svelte-components/src/dialogs/MessageDialog.svelte +++ b/src/svelte-components/src/dialogs/MessageDialog.svelte @@ -1,8 +1,11 @@ {title} + - + {message} + + {#if !noaction} + + + + {/if} diff --git a/src/svelte-components/src/dialogs/MoveToZeroDialog.svelte b/src/svelte-components/src/dialogs/MoveToZeroDialog.svelte new file mode 100644 index 0000000..29079fe --- /dev/null +++ b/src/svelte-components/src/dialogs/MoveToZeroDialog.svelte @@ -0,0 +1,33 @@ + + + + + Move to {(axes || "").toUpperCase()} origin? + + + + + + + + diff --git a/src/svelte-components/src/dialogs/ProbeDialog.svelte b/src/svelte-components/src/dialogs/ProbeDialog.svelte index cc76844..1fc9a3b 100644 --- a/src/svelte-components/src/dialogs/ProbeDialog.svelte +++ b/src/svelte-components/src/dialogs/ProbeDialog.svelte @@ -1,5 +1,4 @@ - + Rebooting to apply the new screen rotation... diff --git a/src/svelte-components/src/dialogs/SetAxisPositionDialog.svelte b/src/svelte-components/src/dialogs/SetAxisPositionDialog.svelte index 8f49c26..32b2091 100644 --- a/src/svelte-components/src/dialogs/SetAxisPositionDialog.svelte +++ b/src/svelte-components/src/dialogs/SetAxisPositionDialog.svelte @@ -3,6 +3,7 @@ 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 = ""; @@ -42,6 +43,7 @@ label="Position" type="number" bind:value + use={[[virtualKeyboardChange, (newValue) => (value = newValue)]]} spellcheck="false" variant="filled" style="width: 100%;" diff --git a/src/svelte-components/src/dialogs/SetTimeDialog.svelte b/src/svelte-components/src/dialogs/SetTimeDialog.svelte index a294e61..25feebb 100644 --- a/src/svelte-components/src/dialogs/SetTimeDialog.svelte +++ b/src/svelte-components/src/dialogs/SetTimeDialog.svelte @@ -5,6 +5,7 @@ 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; @@ -153,6 +154,7 @@ (value = newValue)]]} label="Time" type="datetime-local" variant="filled" diff --git a/src/svelte-components/src/dialogs/ShutdownDialog.svelte b/src/svelte-components/src/dialogs/ShutdownDialog.svelte new file mode 100644 index 0000000..bf98e86 --- /dev/null +++ b/src/svelte-components/src/dialogs/ShutdownDialog.svelte @@ -0,0 +1,38 @@ + + + + Confirm Shutdown? + + + + + + + + + diff --git a/src/svelte-components/src/dialogs/WifiConnectionDialog.svelte b/src/svelte-components/src/dialogs/WifiConnectionDialog.svelte index 24feaf1..0c167c1 100644 --- a/src/svelte-components/src/dialogs/WifiConnectionDialog.svelte +++ b/src/svelte-components/src/dialogs/WifiConnectionDialog.svelte @@ -7,6 +7,7 @@ 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; @@ -38,7 +39,7 @@ } - + Rebooting to apply Wifi changes... @@ -48,14 +49,16 @@ aria-labelledby="wifi-connection-dialog-title" aria-describedby="wifi-connection-dialog-content" > - {connectToOrDisconnectFrom} {network.Name} + + {connectToOrDisconnectFrom} + {network.Name} + {#if needPassword} (password = newValue)]]} label="Password" spellcheck="false" variant="filled" diff --git a/src/svelte-components/src/lib/CustomActions.ts b/src/svelte-components/src/lib/CustomActions.ts new file mode 100644 index 0000000..a3a405e --- /dev/null +++ b/src/svelte-components/src/lib/CustomActions.ts @@ -0,0 +1,9 @@ +export function virtualKeyboardChange(node: HTMLElement, cb: (value: any) => void) { + const input = node.querySelector("input"); + if (!input) { + console.error("Could not find the textfield's :", node); + throw new Error("Could not find the textfield's "); + } + + input.addEventListener("keyup", () => cb(input.value)); +} diff --git a/src/svelte-components/src/lib/RegexHelpers.ts b/src/svelte-components/src/lib/RegexHelpers.ts new file mode 100644 index 0000000..0cd22b2 --- /dev/null +++ b/src/svelte-components/src/lib/RegexHelpers.ts @@ -0,0 +1,90 @@ +type NumberWithUnit = { + value: number, + metric: boolean, + toMetric: () => number; +} + +function numberWithUnitToMetric() { + return this.metric ? this.value : this.value * 25.4; +} + +function isPojo(value) { + if (value === null || typeof value !== "object") { + return false; + } + + return Object.getPrototypeOf(value) === Object.prototype; +} + +const fractions = [ + { value: 0.75, formatted: "3/4" }, + { value: 0.625, formatted: "5/8" }, + { value: 0.5, formatted: "1/2" }, + { value: 0.375, formatted: "3/8" }, + { value: 0.25, formatted: "1/4" }, + { value: 0.1875, formatted: "3/16" }, + { value: 0.125, formatted: "1/8" }, + { value: 0.09375, formatted: "3/32" }, + { value: 0.0625, formatted: "1/16" }, + { value: 0.03125, formatted: "1/32" }, +]; + +function formatFraction(value: number) { + const fraction = fractions.find(f => f.value === value); + + return fraction ? fraction.formatted : value.toString(); +} + +export const numberWithUnit = { + regex: /^\s*(?:(\d+)\s*\/\s*(\d+)|(\d*\.\d+)|(\d+(?:\.\d+)?))\s*("|in|inch|inches|mm|millimeters)\s*$/, + parse: function (str: string) { + let [, numerator, denominator, decimal1, decimal2, unit]: any = str?.match(numberWithUnit.regex) ?? []; + + numerator = Number.parseFloat(numerator) + denominator = Number.parseFloat(denominator) + decimal1 = Number.parseFloat(decimal1) + decimal2 = Number.parseFloat(decimal2) + + const metric = (unit ?? "").includes("m"); + + switch (true) { + case isFinite(numerator) && isFinite(denominator): + return { + value: numerator / denominator, + metric, + toMetric: numberWithUnitToMetric + }; + + case isFinite(decimal1) && decimal1 !== 0: + return { + value: decimal1, + metric, + toMetric: numberWithUnitToMetric + }; + + case isFinite(decimal2) && decimal2 !== 0: + return { + value: decimal2, + metric, + toMetric: numberWithUnitToMetric + }; + + default: + return undefined; + } + }, + normalize: function (str) { + const value = this.parse(str); + + switch (true) { + case !isPojo(value): + return ""; + + case value.metric: + return `${value.value} mm` + + default: + return `${formatFraction(value.value)} in` + } + } +} diff --git a/src/svelte-components/src/lib/RegisterControllerMethods.ts b/src/svelte-components/src/lib/RegisterControllerMethods.ts index f0b47d9..fe5388a 100644 --- a/src/svelte-components/src/lib/RegisterControllerMethods.ts +++ b/src/svelte-components/src/lib/RegisterControllerMethods.ts @@ -1,7 +1,6 @@ -type ControllerMethods = { +interface RegisterableControllerMethods { stop: () => void; send: (gcode: string) => void; - goto_zero: (x: number, y: number, z: number, a: number) => void; dispatch: (event: string, ...args: any[]) => void; isAxisHomed: (axis: string) => boolean; unhome: (axis: string) => void; @@ -9,11 +8,37 @@ type ControllerMethods = { set_home: (axis: string, value: number) => void; } +interface ControllerMethods extends RegisterableControllerMethods { + gotoZero: (axes: "xy" | "z") => void; +} + export let ControllerMethods: ControllerMethods; -export function registerControllerMethods(methods: Partial) { +export function registerControllerMethods(methods: Partial) { ControllerMethods = { ...ControllerMethods, - ...methods + ...methods, + gotoZero }; } + +function gotoZero(axes: "xy" | "z") { + let axesClause = ""; + switch (axes.toLowerCase()) { + case "xy": + axesClause = "X0Y0"; + break; + + case "z": + axesClause = "Z0"; + break; + + default: + throw new Error(`Invalid axes: ${axes}`); + } + + ControllerMethods.send(` + G90 + G0 ${axesClause} + `); +} \ No newline at end of file diff --git a/src/svelte-components/src/theme/_smui-theme.scss b/src/svelte-components/src/theme/_smui-theme.scss index c5e13b7..df1fdfe 100644 --- a/src/svelte-components/src/theme/_smui-theme.scss +++ b/src/svelte-components/src/theme/_smui-theme.scss @@ -20,6 +20,10 @@ :root { --mdc-theme-text-primary-on-background: #777; + .mdc-dialog .mdc-dialog__container { + transition: margin-bottom 0.5s; + } + .mdc-dialog .mdc-dialog__content { color: #777; } diff --git a/src/svelte-components/vite.config.ts b/src/svelte-components/vite.config.ts index 15a9cd4..eeb6a0e 100644 --- a/src/svelte-components/vite.config.ts +++ b/src/svelte-components/vite.config.ts @@ -15,7 +15,6 @@ export default defineConfig({ } }, build: { - minify: false, target: "chrome60", lib: { entry: resolve(__dirname, 'src/main.ts'),