Merge pull request #72 from dacarley/6
7 - Improved bit selection during probing
This commit is contained in:
15
.devcontainer/Dockerfile
Normal file
15
.devcontainer/Dockerfile
Normal 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
|
||||
26
.devcontainer/devcontainer.json
Normal file
26
.devcontainer/devcontainer.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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+",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
ROOT="$PWD/rpi-root"
|
||||
LOOP=12
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <image> <exec>"
|
||||
@@ -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/"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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`);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -39,7 +39,6 @@ function uuid(length) {
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
$(function () {
|
||||
if (typeof cookie_get('client-id') == 'undefined') {
|
||||
cookie_set('client-id', uuid(), 10000);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.`,
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
script#settings-view-template(type="text/x-template")
|
||||
#settings
|
||||
#settings
|
||||
|
||||
@@ -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('?', '-')
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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
|
||||
|
||||
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()
|
||||
|
||||
2
src/static/js/three.min.js
vendored
2
src/static/js/three.min.js
vendored
@@ -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: "+
|
||||
|
||||
@@ -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;
|
||||
|
||||
}));
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<MessageDialog open={rebooting} title="Rebooting">
|
||||
<MessageDialog open={rebooting} title="Rebooting" noaction>
|
||||
Rebooting to apply the hostname change...
|
||||
</MessageDialog>
|
||||
|
||||
@@ -72,15 +73,12 @@
|
||||
<Content id="change-hostname-dialog-content">
|
||||
<TextField
|
||||
bind:value={hostname}
|
||||
use={[[virtualKeyboardChange, (newValue) => (hostname = newValue)]]}
|
||||
label="New Hostname"
|
||||
spellcheck="false"
|
||||
variant="filled"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
|
||||
<p>
|
||||
<em>Clicking Confirm will reboot the controller to apply the change.</em>
|
||||
</p>
|
||||
</Content>
|
||||
|
||||
<Actions>
|
||||
|
||||
@@ -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<HomeMachineDialogPropsType>();
|
||||
type HomeMachineDialogPropsType = {
|
||||
@@ -49,6 +52,25 @@
|
||||
axis: string;
|
||||
};
|
||||
|
||||
const MoveToZeroDialogProps = writable<MoveToZeroDialogPropsType>();
|
||||
type MoveToZeroDialogPropsType = {
|
||||
open: boolean;
|
||||
axes: "xy" | "z";
|
||||
};
|
||||
|
||||
const ShutdownDialogProps = writable<ShutdownDialogPropsType>();
|
||||
type ShutdownDialogPropsType = {
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
const MessageDialogProps = writable<MessageDialogPropsType>();
|
||||
type MessageDialogPropsType = {
|
||||
open: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
noaction: boolean;
|
||||
};
|
||||
|
||||
export function showDialog(
|
||||
dialog: "HomeMachine",
|
||||
props: Omit<HomeMachineDialogPropsType, "open">
|
||||
@@ -84,6 +106,21 @@
|
||||
props: Omit<SetAxisPositionDialogPropsType, "open">
|
||||
);
|
||||
|
||||
export function showDialog(
|
||||
dialog: "MoveToZero",
|
||||
props: Omit<MoveToZeroDialogPropsType, "open">
|
||||
);
|
||||
|
||||
export function showDialog(
|
||||
dialog: "Shutdown",
|
||||
props: Omit<ShutdownDialogPropsType, "open">
|
||||
);
|
||||
|
||||
export function showDialog(
|
||||
dialog: "Message",
|
||||
props: Omit<MessageDialogPropsType, "open">
|
||||
);
|
||||
|
||||
export function showDialog(dialog: string, props: any) {
|
||||
switch (dialog) {
|
||||
case "HomeMachine":
|
||||
@@ -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}'`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
|
||||
let bodyObserver: MutationObserver;
|
||||
let keyboardObserver: MutationObserver;
|
||||
|
||||
onMount(() => {
|
||||
bodyObserver = new MutationObserver(() => {
|
||||
const virtualKeyboard = document.getElementById(
|
||||
"virtualKeyboardChromeExtension"
|
||||
);
|
||||
|
||||
if (virtualKeyboard) {
|
||||
bodyObserver.disconnect();
|
||||
bodyObserver = undefined;
|
||||
|
||||
const virtualKeyboardOverlay = document.getElementById(
|
||||
"virtualKeyboardChromeExtensionOverlayScrollExtend"
|
||||
);
|
||||
|
||||
keyboardObserver = new MutationObserver(() => {
|
||||
const open = virtualKeyboard.getAttribute("_state") === "open";
|
||||
const keyboardHeight = Number.parseFloat(
|
||||
virtualKeyboardOverlay.style.height
|
||||
);
|
||||
|
||||
const dialogContainers = document.querySelectorAll<HTMLDivElement>(
|
||||
".mdc-dialog .mdc-dialog__container"
|
||||
);
|
||||
|
||||
for (let dialogContainer of dialogContainers) {
|
||||
dialogContainer.style["marginBottom"] = open
|
||||
? `${keyboardHeight}px`
|
||||
: "";
|
||||
}
|
||||
});
|
||||
|
||||
keyboardObserver.observe(virtualKeyboard, { attributes: true });
|
||||
}
|
||||
});
|
||||
|
||||
bodyObserver.observe(document.querySelector("body"), {
|
||||
subtree: false,
|
||||
childList: true,
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (bodyObserver) {
|
||||
bodyObserver.disconnect();
|
||||
bodyObserver = undefined;
|
||||
}
|
||||
|
||||
if (keyboardObserver) {
|
||||
keyboardObserver.disconnect();
|
||||
keyboardObserver = undefined;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<HomeMachineDialog {...$HomeMachineDialogProps} />
|
||||
<ProbeDialog {...$ProbeDialogProps} />
|
||||
<ScreenRotationDialog {...$ScreenRotationDialogProps} />
|
||||
@@ -127,3 +236,6 @@
|
||||
<SetTimeDialog {...$SetTimeDialogProps} />
|
||||
<ManualHomeAxisDialog {...$ManualHomeAxisDialogProps} />
|
||||
<SetAxisPositionDialog {...$SetAxisPositionDialogProps} />
|
||||
<MoveToZeroDialog {...$MoveToZeroDialogProps} />
|
||||
<ShutdownDialog {...$ShutdownDialogProps} />
|
||||
<MessageDialog {...$MessageDialogProps} />
|
||||
|
||||
@@ -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%;"
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<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 title: string;
|
||||
export let title = "";
|
||||
export let message = "";
|
||||
export let noaction = false;
|
||||
</script>
|
||||
|
||||
<Dialog
|
||||
@@ -13,7 +16,16 @@
|
||||
aria-describedby="message-dialog-content"
|
||||
>
|
||||
<Title id="message-dialog-title">{title}</Title>
|
||||
|
||||
<Content id="message-dialog-content">
|
||||
<slot />
|
||||
<slot>{message}</slot>
|
||||
</Content>
|
||||
|
||||
{#if !noaction}
|
||||
<Actions>
|
||||
<Button defaultAction>
|
||||
<Label>OK</Label>
|
||||
</Button>
|
||||
</Actions>
|
||||
{/if}
|
||||
</Dialog>
|
||||
|
||||
33
src/svelte-components/src/dialogs/MoveToZeroDialog.svelte
Normal file
33
src/svelte-components/src/dialogs/MoveToZeroDialog.svelte
Normal 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>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script type="ts">
|
||||
import DimensionInput from "$components/DimensionInput.svelte";
|
||||
import Dialog, { Title, Content, Actions } from "@smui/dialog";
|
||||
import Button, { Label } from "@smui/button";
|
||||
import { waitForChange } from "$lib/StoreHelpers";
|
||||
@@ -13,14 +12,23 @@
|
||||
probingFailed,
|
||||
probingStarted,
|
||||
} from "$lib/ControllerState";
|
||||
import { numberWithUnit } from "$lib/RegexHelpers";
|
||||
import TextFieldWithOptions from "$components/TextFieldWithOptions.svelte";
|
||||
|
||||
type Step =
|
||||
| "None"
|
||||
| "CheckProbe"
|
||||
| "BitDimensions"
|
||||
| "PlaceProbeBlock"
|
||||
| "Probe"
|
||||
| "Done";
|
||||
const ValidSteps = [
|
||||
"None",
|
||||
"CheckProbe",
|
||||
"BitDimensions",
|
||||
"PlaceProbeBlock",
|
||||
"Probe",
|
||||
"Done",
|
||||
] as const;
|
||||
|
||||
type Step = typeof ValidSteps[number];
|
||||
|
||||
function isStep(str): str is Step {
|
||||
return ValidSteps.includes(str);
|
||||
}
|
||||
|
||||
const stepLabels: Record<Step, string> = {
|
||||
None: "",
|
||||
@@ -34,21 +42,16 @@
|
||||
const cancelled = writable(false);
|
||||
const userAcknowledged = writable(false);
|
||||
|
||||
const cutterDiameterOptions = [
|
||||
{ value: 0.5, label: '1/2 "', metric: false },
|
||||
{ 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 },
|
||||
];
|
||||
const imperialBits = ["1/2 in", "1/4 in", "1/8 in", "1/16", "1/32"];
|
||||
const metricBits = ["10 mm", "8 mm", "6 mm", "3 mm"];
|
||||
|
||||
export let open;
|
||||
export let probeType: "xyz" | "z";
|
||||
let currentStep: Step = "None";
|
||||
let cutterDiameter: number;
|
||||
let cutterDiameterString: string = "";
|
||||
let cutterDiameterMetric: number;
|
||||
let showCancelButton = true;
|
||||
let steps: Array<Step> = [];
|
||||
let steps: Step[] = [];
|
||||
let nextButton = {
|
||||
label: "Next",
|
||||
disabled: false,
|
||||
@@ -56,10 +59,12 @@
|
||||
};
|
||||
|
||||
$: metric = $Config.settings?.units === "METRIC";
|
||||
$: cutterDiameterMetric = numberWithUnit
|
||||
.parse(cutterDiameterString)
|
||||
?.toMetric();
|
||||
|
||||
$: if (open) {
|
||||
cutterDiameter =
|
||||
Number.parseFloat(localStorage.getItem("cutterDiameter")) || null;
|
||||
cutterDiameterString = localStorage.getItem("cutterDiameter") ?? "";
|
||||
|
||||
// Svelte appears not to like it when you invoke
|
||||
// an async function from a reactive statement, so we
|
||||
@@ -67,32 +72,31 @@
|
||||
requestAnimationFrame(begin);
|
||||
}
|
||||
|
||||
$: if (cutterDiameter) {
|
||||
$: if (cutterDiameterString) {
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function removeSkippedSteps(steps: Step[]): Step[] {
|
||||
return steps.filter((x) => x);
|
||||
}
|
||||
|
||||
async function begin() {
|
||||
try {
|
||||
$probingActive = true;
|
||||
assertValidProbeType();
|
||||
|
||||
steps = removeSkippedSteps([
|
||||
steps = [
|
||||
"CheckProbe",
|
||||
probeType === "xyz" ? "BitDimensions" : undefined,
|
||||
"PlaceProbeBlock",
|
||||
"Probe",
|
||||
"Done",
|
||||
]);
|
||||
].filter<Step>(isStep);
|
||||
|
||||
await stepCompleted("CheckProbe", probeContacted);
|
||||
|
||||
if (probeType === "xyz") {
|
||||
await stepCompleted("BitDimensions", userAcknowledged);
|
||||
localStorage.setItem("cutterDiameter", cutterDiameter.toString());
|
||||
localStorage.setItem(
|
||||
"cutterDiameter",
|
||||
numberWithUnit.normalize(cutterDiameterString)
|
||||
);
|
||||
}
|
||||
|
||||
await stepCompleted("PlaceProbeBlock", userAcknowledged);
|
||||
@@ -100,7 +104,7 @@
|
||||
await stepCompleted("Done", userAcknowledged);
|
||||
|
||||
if (probeType === "xyz") {
|
||||
ControllerMethods.goto_zero(1, 1, 0, 0);
|
||||
ControllerMethods.gotoZero("xy");
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.message !== "cancelled") {
|
||||
@@ -177,11 +181,7 @@
|
||||
break;
|
||||
|
||||
case "BitDimensions":
|
||||
nextButton.disabled = !(
|
||||
cutterDiameter !== null &&
|
||||
cutterDiameter !== 0 &&
|
||||
isFinite(cutterDiameter)
|
||||
);
|
||||
nextButton.disabled = !isFinite(cutterDiameterMetric);
|
||||
break;
|
||||
|
||||
case "Done":
|
||||
@@ -204,8 +204,8 @@
|
||||
|
||||
const cutterLength = 12.7;
|
||||
const zLift = 1;
|
||||
const xOffset = probeBlockWidth + cutterDiameter / 2.0;
|
||||
const yOffset = probeBlockLength + cutterDiameter / 2.0;
|
||||
const xOffset = probeBlockWidth + cutterDiameterMetric / 2.0;
|
||||
const yOffset = probeBlockLength + cutterDiameterMetric / 2.0;
|
||||
const zOffset = probeBlockHeight;
|
||||
|
||||
if (probeType === "z") {
|
||||
@@ -269,11 +269,11 @@
|
||||
scrimClickAction=""
|
||||
aria-labelledby="probe-dialog-title"
|
||||
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>
|
||||
|
||||
<Content id="probe-dialog-content" style="overflow: visible;">
|
||||
<Content id="probe-dialog-content">
|
||||
<div class="steps">
|
||||
<p><b>Step {steps.indexOf(currentStep) + 1} of {steps.length}</b></p>
|
||||
<ul>
|
||||
@@ -282,48 +282,49 @@
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="current-step">
|
||||
<p>
|
||||
{#if currentStep === "CheckProbe"}
|
||||
Attach the probe magnet to the collet, then touch the probe block to
|
||||
the bit.
|
||||
{:else if currentStep === "BitDimensions"}
|
||||
<DimensionInput
|
||||
label="Cutter diameter"
|
||||
options={cutterDiameterOptions}
|
||||
bind:value={cutterDiameter}
|
||||
{metric}
|
||||
/>
|
||||
{:else if currentStep === "PlaceProbeBlock"}
|
||||
<p>
|
||||
{#if currentStep === "CheckProbe"}
|
||||
Attach the probe magnet to the collet, then touch the probe block to the
|
||||
bit.
|
||||
{:else if currentStep === "BitDimensions"}
|
||||
<TextFieldWithOptions
|
||||
label="Cutter diameter"
|
||||
variant="filled"
|
||||
spellcheck="false"
|
||||
style="width: 100%;"
|
||||
bind:value={cutterDiameterString}
|
||||
options={[imperialBits, metricBits]}
|
||||
valid={isFinite(cutterDiameterMetric)}
|
||||
helperText={`Examples: 1/2", 10 mm, 0.25 in`}
|
||||
/>
|
||||
{:else if currentStep === "PlaceProbeBlock"}
|
||||
{#if probeType === "xyz"}
|
||||
Place the probe block face up, on the lower-left corner of your
|
||||
workpiece.
|
||||
{:else}
|
||||
Place the probe block face down, with the bit above the recess.
|
||||
{/if}
|
||||
{:else if currentStep === "Probe"}
|
||||
Probing in progress...
|
||||
{:else if currentStep === "Done"}
|
||||
{#if $probingFailed}
|
||||
Could not find the probe block during probing!
|
||||
|
||||
<p>
|
||||
Make sure the tip of the bit is less than {metric ? "25mm" : "1 in"}
|
||||
above the probe block, and try again.
|
||||
</p>
|
||||
{:else}
|
||||
Don't forget to put away the probe!
|
||||
|
||||
{#if probeType === "xyz"}
|
||||
Place the probe block face up, on the lower-left corner of your
|
||||
workpiece.
|
||||
{:else}
|
||||
Place the probe block face down, with the bit above the recess.
|
||||
{/if}
|
||||
{:else if currentStep === "Probe"}
|
||||
Probing in progress...
|
||||
{:else if currentStep === "Done"}
|
||||
{#if $probingFailed}
|
||||
Could not find the probe block during probing!
|
||||
<p>The machine will now move to the XY origin.</p>
|
||||
|
||||
<p>
|
||||
Make sure the tip of the bit is less than {metric
|
||||
? "25mm"
|
||||
: "1 in"} above the probe block, and try again.
|
||||
</p>
|
||||
{:else}
|
||||
Don't forget to put away the probe!
|
||||
|
||||
{#if probeType === "xyz"}
|
||||
<p>The machine will now move to the XY origin.</p>
|
||||
|
||||
<p>Watch your hands!</p>
|
||||
{/if}
|
||||
<p>Watch your hands!</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</p>
|
||||
</Content>
|
||||
|
||||
<Actions>
|
||||
@@ -358,12 +359,9 @@
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.current-step {
|
||||
flex-grow: 1;
|
||||
|
||||
.mdc-text-field {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.bit-dimensions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.steps {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<MessageDialog open={rebooting} title="Rebooting">
|
||||
<MessageDialog open={rebooting} title="Rebooting" noaction>
|
||||
Rebooting to apply the new screen rotation...
|
||||
</MessageDialog>
|
||||
|
||||
|
||||
@@ -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%;"
|
||||
|
||||
@@ -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 @@
|
||||
<Label>Date & Time</Label>
|
||||
<TextField
|
||||
bind:value
|
||||
use={[[virtualKeyboardChange, (newValue) => (value = newValue)]]}
|
||||
label="Time"
|
||||
type="datetime-local"
|
||||
variant="filled"
|
||||
|
||||
38
src/svelte-components/src/dialogs/ShutdownDialog.svelte
Normal file
38
src/svelte-components/src/dialogs/ShutdownDialog.svelte
Normal 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>
|
||||
@@ -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 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<MessageDialog open={rebooting} title="Rebooting">
|
||||
<MessageDialog open={rebooting} title="Rebooting" noaction>
|
||||
Rebooting to apply Wifi changes...
|
||||
</MessageDialog>
|
||||
|
||||
@@ -48,14 +49,16 @@
|
||||
aria-labelledby="wifi-connection-dialog-title"
|
||||
aria-describedby="wifi-connection-dialog-content"
|
||||
>
|
||||
<Title id="wifi-connection-dialog-title"
|
||||
>{connectToOrDisconnectFrom} {network.Name}</Title
|
||||
>
|
||||
<Title id="wifi-connection-dialog-title">
|
||||
{connectToOrDisconnectFrom}
|
||||
{network.Name}
|
||||
</Title>
|
||||
|
||||
<Content id="wifi-connection-dialog-content">
|
||||
{#if needPassword}
|
||||
<TextField
|
||||
bind:value={password}
|
||||
use={[[virtualKeyboardChange, (newValue) => (password = newValue)]]}
|
||||
label="Password"
|
||||
spellcheck="false"
|
||||
variant="filled"
|
||||
|
||||
9
src/svelte-components/src/lib/CustomActions.ts
Normal file
9
src/svelte-components/src/lib/CustomActions.ts
Normal 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));
|
||||
}
|
||||
90
src/svelte-components/src/lib/RegexHelpers.ts
Normal file
90
src/svelte-components/src/lib/RegexHelpers.ts
Normal 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`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ControllerMethods>) {
|
||||
export function registerControllerMethods(methods: Partial<RegisterableControllerMethods>) {
|
||||
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}
|
||||
`);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
build: {
|
||||
minify: false,
|
||||
target: "chrome60",
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/main.ts'),
|
||||
|
||||
Reference in New Issue
Block a user