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:
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
View File

@@ -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": {

View File

@@ -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+",

View File

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

View File

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

View File

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

View File

@@ -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`);

View File

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

View File

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

View File

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

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

View File

@@ -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.`,

View File

@@ -1,2 +1,2 @@
script#settings-view-template(type="text/x-template")
#settings
#settings

View File

@@ -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('?', '-')

View File

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

View File

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

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
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()

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?
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: "+

View File

@@ -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;
}));

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

View File

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

View File

@@ -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%;"
/>

View File

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

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">
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 {

View File

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

View File

@@ -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%;"

View File

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

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

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;
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}
`);
}

View File

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

View File

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