Merge pull request #72 from dacarley/6

7 - Improved bit selection during probing
This commit is contained in:
David A. Carley
2022-08-23 15:33:41 -07:00
committed by GitHub
39 changed files with 705 additions and 421 deletions

15
.devcontainer/Dockerfile Normal file
View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -8,12 +8,18 @@ Debian Linux are not supported.
On a Debian Linux (9.6.0 stable) system install the required packages: On a Debian Linux (9.6.0 stable) system install the required packages:
sudo apt-get update apt update
sudo apt-get install -y build-essential git wget binfmt-support qemu \ apt upgrade -y
parted gcc-avr avr-libc avrdude pylint3 python3 python3-tornado curl \ 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 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 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 ## Getting the Source Code

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.10b7", "version": "1.0.10b10",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.10b7", "version": "1.0.10b10",
"hasInstallScript": true, "hasInstallScript": true,
"license": "GPL-3.0+", "license": "GPL-3.0+",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.10b7", "version": "1.0.10b10",
"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+",

View File

@@ -1,7 +1,6 @@
#!/bin/bash -ex #!/bin/bash -ex
ROOT="$PWD/rpi-root" ROOT="$PWD/rpi-root"
LOOP=12
if [ $# -lt 1 ]; then if [ $# -lt 1 ]; then
echo "Usage: $0 <image> <exec>" echo "Usage: $0 <image> <exec>"
@@ -9,7 +8,8 @@ if [ $# -lt 1 ]; then
fi fi
IMAGE="$1" IMAGE="$1"
LOOP_DEV=/dev/loop${LOOP} LOOP_BOOT=
LOOP_ROOT=
EXEC= EXEC=
if [ $# -gt 1 ]; then if [ $# -gt 1 ]; then
@@ -26,25 +26,28 @@ fi
# Clean up on EXIT # Clean up on EXIT
function cleanup { function cleanup {
umount "$ROOT"/{dev/pts,dev,sys,proc,boot,mnt/host,} 2>/dev/null || true 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 rmdir "$ROOT" 2>/dev/null || true
} }
trap cleanup EXIT trap cleanup EXIT
# set up image as loop device LOOP_BOOT=`losetup -f`
losetup $LOOP_DEV "$IMAGE" losetup -o 4194304 $LOOP_BOOT "$IMAGE"
partprobe $LOOP_DEV
LOOP_ROOT=`losetup -f`
losetup -o 48234496 $LOOP_ROOT "$IMAGE"
# check and fix filesystems # check and fix filesystems
fsck -f ${LOOP_DEV}p1 fsck -f $LOOP_BOOT
fsck -f ${LOOP_DEV}p2 fsck -f $LOOP_ROOT
# make dir # make dir
mkdir -p "$ROOT" mkdir -p "$ROOT"
# mount partition # mount partition
mount -o rw ${LOOP_DEV}p2 -t ext4 "$ROOT" mount -o rw $LOOP_ROOT -t ext4 "$ROOT"
mount -o rw ${LOOP_DEV}p1 "$ROOT/boot" mount -o rw $LOOP_BOOT "$ROOT/boot"
# mount binds # mount binds
mount --bind /dev "$ROOT/dev/" mount --bind /dev "$ROOT/dev/"

View File

@@ -5,7 +5,7 @@ ccflags-y:=-std=gnu99 -Wno-declaration-after-statement
KPKG=raspberrypi-kernel_1.20171029-1.tar.gz KPKG=raspberrypi-kernel_1.20171029-1.tar.gz
KURL=https://github.com/dbrgn/linux-rpi/archive/$(KPKG) KURL=https://github.com/dbrgn/linux-rpi/archive/$(KPKG)
KDIR=linux-rpi-raspberrypi-kernel_1.20171029-1 KDIR=/tmp/rpi-kernel
export KERNEL=kernel7 export KERNEL=kernel7
KOPTS=ARCH=arm CROSS_COMPILE=$(CROSS) -C $(KDIR) KOPTS=ARCH=arm CROSS_COMPILE=$(CROSS) -C $(KDIR)
@@ -14,7 +14,9 @@ all: $(KDIR)
$(MAKE) $(KOPTS) M=$(DIR) modules $(MAKE) $(KOPTS) M=$(DIR) modules
$(KDIR): $(KPKG) $(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) bcm2709_defconfig
$(MAKE) $(KOPTS) modules_prepare $(MAKE) $(KOPTS) modules_prepare

View File

@@ -46,9 +46,7 @@ module.exports = {
data: function () { data: function () {
return { return {
configRestored: false,
confirmReset: false, confirmReset: false,
configReset: false,
autoCheckUpgrade: true, autoCheckUpgrade: true,
reset_variant: '' reset_variant: ''
} }
@@ -71,29 +69,31 @@ module.exports = {
}, },
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) {
return;
}
var fr = new FileReader(); const fileReader = new FileReader();
fr.onload = function (e) { fileReader.onload = async ({ target }) => {
var config; let config;
try { try {
config = JSON.parse(e.target.result); config = JSON.parse(target.result);
} catch (ex) { } catch (ex) {
api.alert("Invalid config file"); api.alert("Invalid config file");
return; return;
} }
api.put('config/save', config).done(function (data) { try {
await api.put('config/save', config);
this.$dispatch('update'); this.$dispatch('update');
this.configRestored = true; SvelteComponents.showDialog("Message", { title: "Success", message: "Configuration restored" })
} catch (error) {
}.bind(this)).fail(function (error) {
api.alert('Restore failed', error); api.alert('Restore failed', error);
}) }
}.bind(this); }
fr.readAsText(files[0]); fileReader.readAsText(files[0]);
}, },
reset: async function () { reset: async function () {
@@ -107,7 +107,7 @@ module.exports = {
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; SvelteComponents.showDialog("Message", { title: "Success", message: "Configuration restored" })
} catch (err) { } catch (err) {
api.alert('Restore failed'); api.alert('Restore failed');
console.error('Restore failed', err); console.error('Restore failed', err);

View File

@@ -106,8 +106,7 @@ module.exports = new Vue({
firmwareUpgrading: false, firmwareUpgrading: false,
checkedUpgrade: false, checkedUpgrade: false,
firmwareName: "", firmwareName: "",
latestVersion: "", latestVersion: ""
confirmShutdown: false,
}; };
}, },
@@ -283,10 +282,17 @@ module.exports = new Vue({
}, },
show_upgrade: function () { show_upgrade: function () {
if (!this.latestVersion) return false; if (!this.latestVersion) {
return false;
}
return is_newer_version(this.config.version, this.latestVersion); return is_newer_version(this.config.version, this.latestVersion);
}, },
showShutdownDialog: function () {
SvelteComponents.showDialog("Shutdown");
},
update: async function () { update: async function () {
const config = await api.get("config/load"); const config = await api.get("config/load");
@@ -303,16 +309,6 @@ module.exports = new Vue({
SvelteComponents.handleConfigUpdate(this.config); SvelteComponents.handleConfigUpdate(this.config);
}, },
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`);

View File

@@ -39,17 +39,7 @@ module.exports = {
jog_adjust: parseInt(cookie.get('jog-adjust', 2)), jog_adjust: parseInt(cookie.get('jog-adjust', 2)),
deleteGCode: false, deleteGCode: false,
tab: 'auto', tab: 'auto',
toolpath_msg: {
x: false,
y: false,
z: false,
a: false,
b: false,
c: false
},
ask_home: true, ask_home: true,
ask_zero_xy_msg: false,
ask_zero_z_msg: false,
showGcodeMessage: false showGcodeMessage: false
} }
}, },
@@ -244,7 +234,6 @@ module.exports = {
SvelteComponents.registerControllerMethods({ SvelteComponents.registerControllerMethods({
stop: (...args) => this.stop(...args), stop: (...args) => this.stop(...args),
send: (...args) => this.send(...args), send: (...args) => this.send(...args),
goto_zero: (...args) => this.goto_zero(...args),
isAxisHomed: (axis) => this[axis].homed, isAxisHomed: (axis) => this[axis].homed,
unhome: (...args) => this.unhome(...args), unhome: (...args) => this.unhome(...args),
set_position: (...args) => this.set_position(...args), set_position: (...args) => this.set_position(...args),
@@ -253,18 +242,6 @@ module.exports = {
}, },
methods: { 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) { getJogIncrStyle(value) {
const weight = `font-weight:${this.jog_incr === value ? 'bold' : 'normal'}`; const weight = `font-weight:${this.jog_incr === value ? 'bold' : 'normal'}`;
const color = this.jog_incr === value ? "color:#0078e7" : ""; const color = this.jog_incr === value ? "color:#0078e7" : "";
@@ -436,8 +413,12 @@ module.exports = {
SvelteComponents.showDialog("SetAxisPosition", { axis }); SvelteComponents.showDialog("SetAxisPosition", { axis });
}, },
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) {

View File

@@ -39,7 +39,6 @@ function uuid(length) {
return s return s
} }
$(function () { $(function () {
if (typeof cookie_get('client-id') == 'undefined') { if (typeof cookie_get('client-id') == 'undefined') {
cookie_set('client-id', uuid(), 10000); cookie_set('client-id', uuid(), 10000);

View File

@@ -65,16 +65,8 @@ 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 .header

View File

@@ -45,9 +45,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")
@@ -70,10 +67,6 @@ script#admin-general-view-template(type="text/x-template")
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="reset") Reset
message(:show.sync="configReset")
h3(slot="header") Success
p(slot="body") Configuration reset.
h2 Debugging h2 Debugging
a(href="/api/log", target="_blank") a(href="/api/log", target="_blank")
button.pure-button.pure-button-primary View Log button.pure-button.pure-button-primary View Log

View File

@@ -10,32 +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_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%;") 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")
@@ -60,12 +34,12 @@ script#control-view-template(type="text/x-template")
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(@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(@click="ask_zero_xy_msg = true") button(@click="showMoveToZeroDialog('xy')")
.fa.fa-bullseye(style="font-size: 173%") .fa.fa-bullseye(style="font-size: 173%")
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(@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(@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(@click="jog_fn(-1,-1,0,0)") button(@click="jog_fn(-1,-1,0,0)")
@@ -133,20 +107,10 @@ 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}}
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",
title=`Set {{'${axis}' | upper}} axis position.`, title=`Set {{'${axis}' | upper}} axis position.`,

View File

@@ -5,7 +5,7 @@ import glob
import tornado 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:
@@ -20,7 +20,8 @@ class FileHandler(bbctrl.APIHandler):
if self.request.method == 'PUT': if self.request.method == 'PUT':
self.request.connection.set_max_body_size(2 ** 30) 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('#', '-') \ .replace('#', '-') \
.replace('?', '-') .replace('?', '-')

View File

@@ -37,7 +37,7 @@ class Jog(inevent.JogHandler):
config = { config = {
"Logitech Logitech RumblePad 2 USB": { "Logitech Logitech RumblePad 2 USB": {
"deadband": 0.1, "deadband": 0.15,
"axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z], "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
"dir": [1, -1, -1, 1], "dir": [1, -1, -1, 1],
"arrows": [ABS_HAT0X, ABS_HAT0Y], "arrows": [ABS_HAT0X, ABS_HAT0Y],
@@ -46,7 +46,7 @@ class Jog(inevent.JogHandler):
}, },
"default": { "default": {
"deadband": 0.1, "deadband": 0.15,
"axes": [ABS_X, ABS_Y, ABS_RY, ABS_RX], "axes": [ABS_X, ABS_Y, ABS_RY, ABS_RX],
"dir": [1, -1, -1, 1], "dir": [1, -1, -1, 1],
"arrows": [ABS_HAT0X, ABS_HAT0Y], "arrows": [ABS_HAT0X, ABS_HAT0Y],

View File

@@ -4,9 +4,10 @@ import json
import uuid import uuid
import os import os
import socket import socket
import bbctrl
import iw_parse import iw_parse
from tornado import gen import threading
import subprocess
import time
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
@@ -20,6 +21,7 @@ def call_get_output(cmd):
class UploadChangeHandler(FileSystemEventHandler): class UploadChangeHandler(FileSystemEventHandler):
def __init__(self, state): def __init__(self, state):
self.state = state self.state = state
@@ -28,7 +30,9 @@ class UploadChangeHandler(FileSystemEventHandler):
class State(object): class State(object):
def __init__(self, ctrl): def __init__(self, ctrl):
self.lock = threading.Lock()
self.ctrl = ctrl self.ctrl = ctrl
self.log = ctrl.log.get('State') self.log = ctrl.log.get('State')
@@ -72,14 +76,15 @@ class State(object):
self.load_files() self.load_files()
observer = Observer() observer = Observer()
observer.schedule(UploadChangeHandler( observer.schedule(UploadChangeHandler(self),
self), self.ctrl.get_upload(), recursive=True) self.ctrl.get_upload(),
recursive=True)
observer.start() observer.start()
self._updateNetworkInfo() threading.Thread(target=self._updateNetworkInfo, daemon=True).start()
@gen.coroutine
def _updateNetworkInfo(self): def _updateNetworkInfo(self):
while True:
try: try:
ipAddresses = call_get_output(['hostname', '-I']).split() ipAddresses = call_get_output(['hostname', '-I']).split()
except: except:
@@ -104,7 +109,7 @@ class State(object):
'wifi': wifi 'wifi': wifi
}) })
self.timeout = self.ctrl.ioloop.call_later(5, self._updateNetworkInfo) time.sleep(5)
def reset(self): def reset(self):
# Unhome all motors # Unhome all motors
@@ -217,6 +222,8 @@ class State(object):
self.callbacks[self.resolve(name)] = cb self.callbacks[self.resolve(name)] = cb
def set(self, name, value): def set(self, name, value):
self.lock.acquire()
try:
name = self.resolve(name) name = self.resolve(name)
if not name in self.vars or self.vars[name] != value: if not name in self.vars or self.vars[name] != value:
@@ -225,7 +232,10 @@ class State(object):
# Trigger listener notify # Trigger listener notify
if self.timeout is None: if self.timeout is None:
self.timeout = self.ctrl.ioloop.call_later(0.25, self._notify) self.timeout = self.ctrl.ioloop.call_later(
0.25, self._notify)
finally:
self.lock.release()
def update(self, update): def update(self, update):
for name, value in update.items(): for name, value in update.items():

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 logging import logging
from inevent.Constants import * from inevent.Constants import *
@@ -97,7 +70,31 @@ class JogHandler:
# Process event # Process event
if event.type == EV_ABS and event.code in config['axes']: 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']: elif event.type == EV_KEY and event.code in config['speed']:
old_speed = self.speed old_speed = self.speed
@@ -115,22 +112,4 @@ class JogHandler:
log.debug(event_to_string(event, state)) 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() if changed: self.changed()

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

@@ -1,91 +0,0 @@
<script lang="ts">
import TextField from "@smui/textfield";
import Select, { Option } from "@smui/select";
import type { MenuComponentDev } from "@smui/menu";
import Menu from "@smui/menu";
import List, { Item, Text } from "@smui/list";
import { onMount } from "svelte";
import { set_input_value } from "svelte/internal";
let menu: MenuComponentDev;
type Option = {
value: number;
label: string;
metric: boolean;
};
export let label: string;
export let value: number;
export let metric: boolean;
export let options: Option[];
let textValue = "";
$: if (textValue) {
value = Number.parseFloat(textValue) || null;
}
onMount(() => {
textValue = value?.toString() || "";
});
function onOptionSelected(option: Option) {
textValue = option.value.toString();
metric = option.metric;
}
function filterKeys(event) {
const input = event.target;
if (input.value.match(/[^0-9.]/)) {
input.value = input.value.replace(/[^0-9.]/g, "");
}
if (input.value.match(/\.[^.]*\./)) {
input.value = input.value.replace(/(\.[^.]*)\./g, "$1");
}
}
</script>
<div>
<div class="value-and-unit">
<TextField
{label}
on:keypress={filterKeys}
on:keyup={filterKeys}
bind:value={textValue}
on:click={() => menu.setOpen(true)}
/>
<Select bind:value={metric}>
<Option value={true}>mm</Option>
<Option value={false}>in</Option>
</Select>
</div>
<Menu bind:this={menu} anchorCorner="BOTTOM_LEFT">
<List>
{#each options as option}
<Item on:SMUI:action={() => onOptionSelected(option)}>
<Text>{option.label}</Text>
</Item>
{/each}
</List>
</Menu>
</div>
<style lang="scss">
:global {
.value-and-unit {
display: flex;
column-gap: 10px;
.mdc-select {
max-width: 60px;
}
.smui-select--standard .mdc-select__dropdown-icon {
margin-left: 0;
}
}
}
</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, (newValue) => (value = newValue)]]}
{...$$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: 35px;
}
}
}
</style>

View File

@@ -4,6 +4,7 @@
import TextField from "@smui/textfield"; import TextField from "@smui/textfield";
import MessageDialog from "$dialogs/MessageDialog.svelte"; import MessageDialog from "$dialogs/MessageDialog.svelte";
import * as api from "$lib/api"; import * as api from "$lib/api";
import { virtualKeyboardChange } from "$lib/CustomActions";
// https://man7.org/linux/man-pages/man7/hostname.7.html // https://man7.org/linux/man-pages/man7/hostname.7.html
// //
@@ -57,7 +58,7 @@
} }
</script> </script>
<MessageDialog open={rebooting} title="Rebooting"> <MessageDialog open={rebooting} title="Rebooting" noaction>
Rebooting to apply the hostname change... Rebooting to apply the hostname change...
</MessageDialog> </MessageDialog>
@@ -72,15 +73,12 @@
<Content id="change-hostname-dialog-content"> <Content id="change-hostname-dialog-content">
<TextField <TextField
bind:value={hostname} bind:value={hostname}
use={[[virtualKeyboardChange, (newValue) => (hostname = newValue)]]}
label="New Hostname" label="New Hostname"
spellcheck="false" spellcheck="false"
variant="filled" variant="filled"
style="width: 100%;" style="width: 100%;"
/> />
<p>
<em>Clicking Confirm will reboot the controller to apply the change.</em>
</p>
</Content> </Content>
<Actions> <Actions>

View File

@@ -7,6 +7,9 @@
import SetTimeDialog from "./SetTimeDialog.svelte"; import SetTimeDialog from "./SetTimeDialog.svelte";
import ManualHomeAxisDialog from "./ManualHomeAxisDialog.svelte"; import ManualHomeAxisDialog from "./ManualHomeAxisDialog.svelte";
import SetAxisPositionDialog from "./SetAxisPositionDialog.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>(); const HomeMachineDialogProps = writable<HomeMachineDialogPropsType>();
type HomeMachineDialogPropsType = { type HomeMachineDialogPropsType = {
@@ -49,6 +52,25 @@
axis: string; 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( export function showDialog(
dialog: "HomeMachine", dialog: "HomeMachine",
props: Omit<HomeMachineDialogPropsType, "open"> props: Omit<HomeMachineDialogPropsType, "open">
@@ -84,6 +106,21 @@
props: Omit<SetAxisPositionDialogPropsType, "open"> 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) { export function showDialog(dialog: string, props: any) {
switch (dialog) { switch (dialog) {
case "HomeMachine": case "HomeMachine":
@@ -114,12 +151,84 @@
SetAxisPositionDialogProps.set({ ...props, open: true }); SetAxisPositionDialogProps.set({ ...props, open: true });
break; 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: default:
throw new Error(`Unknown dialog '${dialog}`); throw new Error(`Unknown dialog '${dialog}'`);
} }
} }
</script> </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} /> <HomeMachineDialog {...$HomeMachineDialogProps} />
<ProbeDialog {...$ProbeDialogProps} /> <ProbeDialog {...$ProbeDialogProps} />
<ScreenRotationDialog {...$ScreenRotationDialogProps} /> <ScreenRotationDialog {...$ScreenRotationDialogProps} />
@@ -127,3 +236,6 @@
<SetTimeDialog {...$SetTimeDialogProps} /> <SetTimeDialog {...$SetTimeDialogProps} />
<ManualHomeAxisDialog {...$ManualHomeAxisDialogProps} /> <ManualHomeAxisDialog {...$ManualHomeAxisDialogProps} />
<SetAxisPositionDialog {...$SetAxisPositionDialogProps} /> <SetAxisPositionDialog {...$SetAxisPositionDialogProps} />
<MoveToZeroDialog {...$MoveToZeroDialogProps} />
<ShutdownDialog {...$ShutdownDialogProps} />
<MessageDialog {...$MessageDialogProps} />

View File

@@ -3,6 +3,7 @@
import TextField from "@smui/textfield"; import TextField from "@smui/textfield";
import Button, { Label } from "@smui/button"; import Button, { Label } from "@smui/button";
import { ControllerMethods } from "$lib/RegisterControllerMethods"; import { ControllerMethods } from "$lib/RegisterControllerMethods";
import { virtualKeyboardChange } from "$lib/CustomActions";
export let open: boolean; export let open: boolean;
export let axis = ""; export let axis = "";
@@ -30,6 +31,7 @@
label="Absolute" label="Absolute"
type="number" type="number"
bind:value bind:value
use={[[virtualKeyboardChange, (newValue) => (value = newValue)]]}
variant="filled" variant="filled"
style="width: 100%;" style="width: 100%;"
/> />

View File

@@ -1,8 +1,11 @@
<script lang="ts"> <script lang="ts">
import Dialog, { Title, Content } from "@smui/dialog"; import Dialog, { Title, Content, Actions } from "@smui/dialog";
import Button, { Label } from "@smui/button";
export let open: boolean; export let open: boolean;
export let title: string; export let title = "";
export let message = "";
export let noaction = false;
</script> </script>
<Dialog <Dialog
@@ -13,7 +16,16 @@
aria-describedby="message-dialog-content" aria-describedby="message-dialog-content"
> >
<Title id="message-dialog-title">{title}</Title> <Title id="message-dialog-title">{title}</Title>
<Content id="message-dialog-content"> <Content id="message-dialog-content">
<slot /> <slot>{message}</slot>
</Content> </Content>
{#if !noaction}
<Actions>
<Button defaultAction>
<Label>OK</Label>
</Button>
</Actions>
{/if}
</Dialog> </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

@@ -1,5 +1,4 @@
<script type="ts"> <script type="ts">
import DimensionInput from "$components/DimensionInput.svelte";
import Dialog, { Title, Content, Actions } from "@smui/dialog"; import Dialog, { Title, Content, Actions } from "@smui/dialog";
import Button, { Label } from "@smui/button"; import Button, { Label } from "@smui/button";
import { waitForChange } from "$lib/StoreHelpers"; import { waitForChange } from "$lib/StoreHelpers";
@@ -13,14 +12,23 @@
probingFailed, probingFailed,
probingStarted, probingStarted,
} from "$lib/ControllerState"; } from "$lib/ControllerState";
import { numberWithUnit } from "$lib/RegexHelpers";
import TextFieldWithOptions from "$components/TextFieldWithOptions.svelte";
type Step = const ValidSteps = [
| "None" "None",
| "CheckProbe" "CheckProbe",
| "BitDimensions" "BitDimensions",
| "PlaceProbeBlock" "PlaceProbeBlock",
| "Probe" "Probe",
| "Done"; "Done",
] as const;
type Step = typeof ValidSteps[number];
function isStep(str): str is Step {
return ValidSteps.includes(str);
}
const stepLabels: Record<Step, string> = { const stepLabels: Record<Step, string> = {
None: "", None: "",
@@ -34,21 +42,16 @@
const cancelled = writable(false); const cancelled = writable(false);
const userAcknowledged = writable(false); const userAcknowledged = writable(false);
const cutterDiameterOptions = [ const imperialBits = ["1/2 in", "1/4 in", "1/8 in", "1/16", "1/32"];
{ value: 0.5, label: '1/2 "', metric: false }, const metricBits = ["10 mm", "8 mm", "6 mm", "3 mm"];
{ value: 0.25, label: '1/4 "', metric: false },
{ value: 0.125, label: '1/8 "', metric: false },
{ value: 10, label: "10 mm", metric: true },
{ value: 6, label: "6 mm", metric: true },
{ value: 3, label: "10 mm", metric: true },
];
export let open; export let open;
export let probeType: "xyz" | "z"; export let probeType: "xyz" | "z";
let currentStep: Step = "None"; let currentStep: Step = "None";
let cutterDiameter: number; let cutterDiameterString: string = "";
let cutterDiameterMetric: number;
let showCancelButton = true; let showCancelButton = true;
let steps: Array<Step> = []; let steps: Step[] = [];
let nextButton = { let nextButton = {
label: "Next", label: "Next",
disabled: false, disabled: false,
@@ -56,10 +59,12 @@
}; };
$: metric = $Config.settings?.units === "METRIC"; $: metric = $Config.settings?.units === "METRIC";
$: cutterDiameterMetric = numberWithUnit
.parse(cutterDiameterString)
?.toMetric();
$: if (open) { $: if (open) {
cutterDiameter = cutterDiameterString = localStorage.getItem("cutterDiameter") ?? "";
Number.parseFloat(localStorage.getItem("cutterDiameter")) || null;
// Svelte appears not to like it when you invoke // Svelte appears not to like it when you invoke
// an async function from a reactive statement, so we // an async function from a reactive statement, so we
@@ -67,32 +72,31 @@
requestAnimationFrame(begin); requestAnimationFrame(begin);
} }
$: if (cutterDiameter) { $: if (cutterDiameterString) {
updateButtons(); updateButtons();
} }
function removeSkippedSteps(steps: Step[]): Step[] {
return steps.filter((x) => x);
}
async function begin() { async function begin() {
try { try {
$probingActive = true; $probingActive = true;
assertValidProbeType(); assertValidProbeType();
steps = removeSkippedSteps([ steps = [
"CheckProbe", "CheckProbe",
probeType === "xyz" ? "BitDimensions" : undefined, probeType === "xyz" ? "BitDimensions" : undefined,
"PlaceProbeBlock", "PlaceProbeBlock",
"Probe", "Probe",
"Done", "Done",
]); ].filter<Step>(isStep);
await stepCompleted("CheckProbe", probeContacted); await stepCompleted("CheckProbe", probeContacted);
if (probeType === "xyz") { if (probeType === "xyz") {
await stepCompleted("BitDimensions", userAcknowledged); await stepCompleted("BitDimensions", userAcknowledged);
localStorage.setItem("cutterDiameter", cutterDiameter.toString()); localStorage.setItem(
"cutterDiameter",
numberWithUnit.normalize(cutterDiameterString)
);
} }
await stepCompleted("PlaceProbeBlock", userAcknowledged); await stepCompleted("PlaceProbeBlock", userAcknowledged);
@@ -100,7 +104,7 @@
await stepCompleted("Done", userAcknowledged); await stepCompleted("Done", userAcknowledged);
if (probeType === "xyz") { if (probeType === "xyz") {
ControllerMethods.goto_zero(1, 1, 0, 0); ControllerMethods.gotoZero("xy");
} }
} catch (err) { } catch (err) {
if (err.message !== "cancelled") { if (err.message !== "cancelled") {
@@ -177,11 +181,7 @@
break; break;
case "BitDimensions": case "BitDimensions":
nextButton.disabled = !( nextButton.disabled = !isFinite(cutterDiameterMetric);
cutterDiameter !== null &&
cutterDiameter !== 0 &&
isFinite(cutterDiameter)
);
break; break;
case "Done": case "Done":
@@ -204,8 +204,8 @@
const cutterLength = 12.7; const cutterLength = 12.7;
const zLift = 1; const zLift = 1;
const xOffset = probeBlockWidth + cutterDiameter / 2.0; const xOffset = probeBlockWidth + cutterDiameterMetric / 2.0;
const yOffset = probeBlockLength + cutterDiameter / 2.0; const yOffset = probeBlockLength + cutterDiameterMetric / 2.0;
const zOffset = probeBlockHeight; const zOffset = probeBlockHeight;
if (probeType === "z") { if (probeType === "z") {
@@ -269,11 +269,11 @@
scrimClickAction="" scrimClickAction=""
aria-labelledby="probe-dialog-title" aria-labelledby="probe-dialog-title"
aria-describedby="probe-dialog-content" aria-describedby="probe-dialog-content"
surface$style="width: 700px; height: 400px; max-width: calc(100vw - 32px); overflow: visible;" surface$style="width: 700px; max-width: calc(100vw - 32px);"
> >
<Title id="probe-dialog-title">Probing {probeType?.toUpperCase()}</Title> <Title id="probe-dialog-title">Probing {probeType?.toUpperCase()}</Title>
<Content id="probe-dialog-content" style="overflow: visible;"> <Content id="probe-dialog-content">
<div class="steps"> <div class="steps">
<p><b>Step {steps.indexOf(currentStep) + 1} of {steps.length}</b></p> <p><b>Step {steps.indexOf(currentStep) + 1} of {steps.length}</b></p>
<ul> <ul>
@@ -282,17 +282,20 @@
{/each} {/each}
</ul> </ul>
</div> </div>
<div class="current-step">
<p> <p>
{#if currentStep === "CheckProbe"} {#if currentStep === "CheckProbe"}
Attach the probe magnet to the collet, then touch the probe block to Attach the probe magnet to the collet, then touch the probe block to the
the bit. bit.
{:else if currentStep === "BitDimensions"} {:else if currentStep === "BitDimensions"}
<DimensionInput <TextFieldWithOptions
label="Cutter diameter" label="Cutter diameter"
options={cutterDiameterOptions} variant="filled"
bind:value={cutterDiameter} spellcheck="false"
{metric} style="width: 100%;"
bind:value={cutterDiameterString}
options={[imperialBits, metricBits]}
valid={isFinite(cutterDiameterMetric)}
helperText={`Examples: 1/2", 10 mm, 0.25 in`}
/> />
{:else if currentStep === "PlaceProbeBlock"} {:else if currentStep === "PlaceProbeBlock"}
{#if probeType === "xyz"} {#if probeType === "xyz"}
@@ -308,9 +311,8 @@
Could not find the probe block during probing! Could not find the probe block during probing!
<p> <p>
Make sure the tip of the bit is less than {metric Make sure the tip of the bit is less than {metric ? "25mm" : "1 in"}
? "25mm" above the probe block, and try again.
: "1 in"} above the probe block, and try again.
</p> </p>
{:else} {:else}
Don't forget to put away the probe! Don't forget to put away the probe!
@@ -323,7 +325,6 @@
{/if} {/if}
{/if} {/if}
</p> </p>
</div>
</Content> </Content>
<Actions> <Actions>
@@ -358,12 +359,9 @@
flex-direction: row; flex-direction: row;
} }
.current-step { .bit-dimensions {
flex-grow: 1; display: flex;
flex-direction: column;
.mdc-text-field {
margin-bottom: 20px;
}
} }
.steps { .steps {

View File

@@ -29,7 +29,7 @@
} }
</script> </script>
<MessageDialog open={rebooting} title="Rebooting"> <MessageDialog open={rebooting} title="Rebooting" noaction>
Rebooting to apply the new screen rotation... Rebooting to apply the new screen rotation...
</MessageDialog> </MessageDialog>

View File

@@ -3,6 +3,7 @@
import TextField from "@smui/textfield"; import TextField from "@smui/textfield";
import Button, { Label } from "@smui/button"; import Button, { Label } from "@smui/button";
import { ControllerMethods } from "$lib/RegisterControllerMethods"; import { ControllerMethods } from "$lib/RegisterControllerMethods";
import { virtualKeyboardChange } from "$lib/CustomActions";
export let open: boolean; export let open: boolean;
export let axis = ""; export let axis = "";
@@ -42,6 +43,7 @@
label="Position" label="Position"
type="number" type="number"
bind:value bind:value
use={[[virtualKeyboardChange, (newValue) => (value = newValue)]]}
spellcheck="false" spellcheck="false"
variant="filled" variant="filled"
style="width: 100%;" style="width: 100%;"

View File

@@ -5,6 +5,7 @@
import CircularProgress from "@smui/circular-progress"; import CircularProgress from "@smui/circular-progress";
import VirtualList from "svelte-tiny-virtual-list"; import VirtualList from "svelte-tiny-virtual-list";
import * as api from "$lib/api"; import * as api from "$lib/api";
import { virtualKeyboardChange } from "$lib/CustomActions";
const itemHeight = 35; const itemHeight = 35;
@@ -153,6 +154,7 @@
<Label>Date & Time</Label> <Label>Date & Time</Label>
<TextField <TextField
bind:value bind:value
use={[[virtualKeyboardChange, (newValue) => (value = newValue)]]}
label="Time" label="Time"
type="datetime-local" type="datetime-local"
variant="filled" variant="filled"

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

@@ -7,6 +7,7 @@
import MessageDialog from "$dialogs/MessageDialog.svelte"; import MessageDialog from "$dialogs/MessageDialog.svelte";
import type { WifiNetwork } from "$lib/NetworkInfo"; import type { WifiNetwork } from "$lib/NetworkInfo";
import * as api from "$lib/api"; import * as api from "$lib/api";
import { virtualKeyboardChange } from "$lib/CustomActions";
export let open = false; export let open = false;
export let network: WifiNetwork; export let network: WifiNetwork;
@@ -38,7 +39,7 @@
} }
</script> </script>
<MessageDialog open={rebooting} title="Rebooting"> <MessageDialog open={rebooting} title="Rebooting" noaction>
Rebooting to apply Wifi changes... Rebooting to apply Wifi changes...
</MessageDialog> </MessageDialog>
@@ -48,14 +49,16 @@
aria-labelledby="wifi-connection-dialog-title" aria-labelledby="wifi-connection-dialog-title"
aria-describedby="wifi-connection-dialog-content" aria-describedby="wifi-connection-dialog-content"
> >
<Title id="wifi-connection-dialog-title" <Title id="wifi-connection-dialog-title">
>{connectToOrDisconnectFrom} {network.Name}</Title {connectToOrDisconnectFrom}
> {network.Name}
</Title>
<Content id="wifi-connection-dialog-content"> <Content id="wifi-connection-dialog-content">
{#if needPassword} {#if needPassword}
<TextField <TextField
bind:value={password} bind:value={password}
use={[[virtualKeyboardChange, (newValue) => (password = newValue)]]}
label="Password" label="Password"
spellcheck="false" spellcheck="false"
variant="filled" variant="filled"

View File

@@ -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 <input>:", node);
throw new Error("Could not find the textfield's <input>");
}
input.addEventListener("keyup", () => cb(input.value));
}

View File

@@ -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`
}
}
}

View File

@@ -1,7 +1,6 @@
type ControllerMethods = { interface RegisterableControllerMethods {
stop: () => void; stop: () => void;
send: (gcode: string) => void; send: (gcode: string) => void;
goto_zero: (x: number, y: number, z: number, a: number) => void;
dispatch: (event: string, ...args: any[]) => void; dispatch: (event: string, ...args: any[]) => void;
isAxisHomed: (axis: string) => boolean; isAxisHomed: (axis: string) => boolean;
unhome: (axis: string) => void; unhome: (axis: string) => void;
@@ -9,11 +8,37 @@ type ControllerMethods = {
set_home: (axis: string, value: number) => void; set_home: (axis: string, value: number) => void;
} }
interface ControllerMethods extends RegisterableControllerMethods {
gotoZero: (axes: "xy" | "z") => void;
}
export let ControllerMethods: ControllerMethods; export let ControllerMethods: ControllerMethods;
export function registerControllerMethods(methods: Partial<ControllerMethods>) { export function registerControllerMethods(methods: Partial<RegisterableControllerMethods>) {
ControllerMethods = { ControllerMethods = {
...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}
`);
}

View File

@@ -20,6 +20,10 @@
:root { :root {
--mdc-theme-text-primary-on-background: #777; --mdc-theme-text-primary-on-background: #777;
.mdc-dialog .mdc-dialog__container {
transition: margin-bottom 0.5s;
}
.mdc-dialog .mdc-dialog__content { .mdc-dialog .mdc-dialog__content {
color: #777; color: #777;
} }

View File

@@ -15,7 +15,6 @@ export default defineConfig({
} }
}, },
build: { build: {
minify: false,
target: "chrome60", target: "chrome60",
lib: { lib: {
entry: resolve(__dirname, 'src/main.ts'), entry: resolve(__dirname, 'src/main.ts'),