Merge pull request #70 from dacarley/4

5 - Screen rotation support
This commit is contained in:
David A. Carley
2022-08-23 15:33:15 -07:00
committed by GitHub
26 changed files with 255 additions and 189 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "bbctrl",
"version": "1.0.10b2",
"version": "1.0.10b4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bbctrl",
"version": "1.0.10b2",
"version": "1.0.10b4",
"license": "GPL-3.0+",
"dependencies": {
"browserify": "^17.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "bbctrl",
"version": "1.0.10b2",
"version": "1.0.10b4",
"homepage": "https://onefinitycnc.com/",
"repository": "https://github.com/OneFinityCNC/onefinity",
"license": "GPL-3.0+",

View File

@@ -1,32 +0,0 @@
#!/bin/bash
if [ $# != 3 ]; then
echo "Usage: $0 <width> <height> <rotation>"
exit 1
fi
WIDTH="$1"
HEIGHT="$2"
ROTATION="$3"
if [[ ! "$WIDTH" =~ ^[0-9]+$ ]]; then
echo "Invalid width '$WIDTH'."
exit 1
fi
if [[ ! "$HEIGHT" =~ ^[0-9]+$ ]]; then
echo "Invalid height '$HEIGHT'."
exit 1
fi
if [[ ! "$ROTATION" =~ ^[0-3]$ ]]; then
echo "Invalid rotation '$ROTATION'."
exit 1
fi
OPTIONS="framebuffer_width=$WIDTH "
OPTIONS+="framebuffer_height=$HEIGHT "
OPTIONS+="display_rotate=$ROTATION"
edit-boot-config $OPTIONS

View File

@@ -29,8 +29,7 @@ if $UPDATE_AVR; then
fi
# Update config.txt
./scripts/edit-boot-config max_usb_current=1
./scripts/edit-boot-config config_hdmi_boost=8
./scripts/edit-boot-config max_usb_current=1 config_hdmi_boost=8 hdmi_force_hotplug=1 hdmi_group=2 hdmi_mode=82
# TODO Enable GPU
#./scripts/edit-boot-config dtoverlay=vc4-kms-v3d

View File

@@ -34,7 +34,6 @@ setup(
'scripts/sethostname',
'scripts/reset-video',
'scripts/config-wifi',
'scripts/config-screen',
'scripts/edit-config',
'scripts/edit-boot-config',
'scripts/browser',

View File

@@ -21,5 +21,11 @@ module.exports = {
this.$dispatch('config-changed');
return false;
}
},
methods: {
showScreenRotationDialog: function () {
SvelteComponents.showDialog("ScreenRotation");
}
}
}

View File

@@ -3,7 +3,6 @@ html(lang="en")
head
meta(charset="utf-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
link(rel="preload" href="/fonts/material-symbols-outlined.woff2" as="font" type="font/woff2" crossorigin)
title Onefinity CNC - Web interface
@@ -16,7 +15,6 @@ html(lang="en")
style: include ../svelte-components/node_modules/svelte-material-ui/bare.css
style: include ../../build/http/svelte-components/smui.css
style: include ../../build/http/svelte-components/style.css
style: include ../../build/http/svelte-components/material-symbols-outlined.css
style: include:stylus ../stylus/style.styl
body(v-cloak)

View File

@@ -61,7 +61,7 @@ script#control-view-template(type="text/x-template")
button(@click="jog_fn(-1,0,0,0)") X-
td(style="height:100px",align="center")
button(@click="ask_zero_xy_msg = true")
.fa.fa-bullseye(style="font-size: 172%")
.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")
@@ -221,7 +221,7 @@ script#control-view-template(type="text/x-template")
| {{message.replace(/^#/, '')}}
tr
th Units
th Display Units
td.units
select(v-model="display_units")
option(value="METRIC") METRIC

View File

@@ -4,16 +4,14 @@ script#settings-view-template(type="text/x-template")
.pure-form.pure-form-aligned
fieldset
h2 Units
h2 Screen
.pure-control-group
label(for="units") units
select(name="units", v-model="display_units")
option(value="METRIC") METRIC
option(value="IMPERIAL") IMPERIAL
label(for="screen-rotation")
button.pure-button(name="screen-rotation", @click="showScreenRotationDialog") Change Screen Rotation
fieldset
h2 Probe Dimensions
templated-input(v-for="templ in template.probe", :name="$key",
templated-input(v-for="templ in template.probe", v-if="$key !== 'probe-diameter'", :name="$key"
:model.sync="config.probe[$key]", :template="templ")
fieldset

View File

@@ -71,6 +71,7 @@ class Ctrl(object):
def configure(self):
# Indirectly configures state via calls to config() and the AVR
self.config.reload()
self.state.init()
def ready(self):
# This is used to synchronize the start of the preplanner

View File

@@ -419,3 +419,12 @@ class State(object):
if switch[1:] == '-max':
return self.get_axis_switch(switch[0], 'max')
raise Exception('Unsupported switch "%s"' % switch)
def init(self):
# Init machine units
metric = self.ctrl.config.get('units', 'METRIC').upper() == 'METRIC'
self.log.info('INIT Metric %d' % metric)
if not 'metric' in self.vars:
self.set('metric', metric)
if not 'imperial' in self.vars:
self.set('imperial', not metric)

View File

@@ -1,4 +1,5 @@
import os
import re
import json
import tornado
import sockjs.tornado
@@ -349,6 +350,46 @@ class JogHandler(bbctrl.APIHandler):
self.get_ctrl().mach.jog(self.json)
displayRotatePattern = re.compile(r'display_rotate\s*=\s*(\d)')
transformationMatrixPattern = re.compile(
r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(\s+Option\s+\"TransformationMatrix\".*?\n)(.*?EndSection)', re.DOTALL)
matchIsTouchscreenPattern = re.compile(
r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(.*?EndSection)', re.DOTALL)
class ScreenRotationHandler(bbctrl.APIHandler):
@gen.coroutine
def get(self):
with open("/boot/config.txt", 'rt') as config:
lines = config.readlines()
for line in lines:
if line.startswith('display_rotate'):
self.write_json({
'rotated': int(displayRotatePattern.search(line).group(1)) != 0
})
return
self.write_json({'rotated': False})
return
@gen.coroutine
def put_ok(self):
rotated = self.json['rotated']
subprocess.Popen(
['/usr/local/bin/edit-boot-config', 'display_rotate={}'.format(2 if rotated else 0)])
with open("/usr/share/X11/xorg.conf.d/40-libinput.conf", 'rt') as config:
text = config.read()
text = transformationMatrixPattern.sub(r'\1\2\3\5', text)
if rotated:
text = matchIsTouchscreenPattern.sub(r'\1\2\3\2Option "TransformationMatrix" "-1 0 1 0 -1 1 0 0 1"\1\4', text)
with open("/usr/share/X11/xorg.conf.d/40-libinput.conf", 'wt') as config:
config.write(text)
subprocess.run('reboot')
# Base class for Web Socket connections
class ClientConnection(object):
def __init__(self, app):
@@ -484,6 +525,7 @@ class Web(tornado.web.Application):
(r'/api/modbus/write', ModbusWriteHandler),
(r'/api/jog', JogHandler),
(r'/api/video', bbctrl.VideoHandler),
(r'/api/screen-rotation', ScreenRotationHandler),
(r'/(.*)', StaticFileHandler,
{'path': bbctrl.get_resource('http/'),
'default_filename': 'index.html'}),

View File

@@ -4,6 +4,9 @@ body
[v-cloak]
display none
.menu-link
z-index unset
tt
color #000
background #eee
@@ -356,6 +359,7 @@ span.unit
.axis
.name
text-transform capitalize
vertical-align middle
.name, .position
font-size 24pt

View File

@@ -1,28 +0,0 @@
/* To browse the icons in material-symbols, see https://marella.me/material-symbols/demo/ */
@font-face {
font-family: "Material Symbols Outlined";
font-style: normal;
font-weight: 100 700;
font-display: block;
src: url("./fonts/material-symbols-outlined.woff2") format("woff2");
}
.material-symbols-outlined {
font-family: "Material Symbols Outlined";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
font-variation-settings: "FILL" 1, "wght" 400, "GRAD" 0, "opsz" 48;
}

View File

@@ -15,21 +15,24 @@
network: {} as WifiNetwork,
};
function getWifiStrengthIcon(network: WifiNetwork) {
function getWifiStrengthStyle(network: WifiNetwork) {
const strength = Math.ceil((Number(network.Quality) / 100) * 4);
switch (strength) {
case 0:
return "clip-path: circle(0px at 12.5px 19px);";
case 1:
return "";
return "clip-path: circle(4px at 12.5px 19px);";
case 2:
return "wifi_1_bar";
return "clip-path: circle(8px at 12.5px 19px);";
case 3:
return "wifi_2_bar";
return "clip-path: circle(14px at 12.5px 19px);";
case 4:
return "wifi";
return "";
}
}
@@ -103,17 +106,16 @@
? 'active'
: ''}"
>
<span class="material-symbols-outlined background"
>wifi</span
>
<span class="material-symbols-outlined">
{getWifiStrengthIcon(network)}
</span>
<span class="fa fa-wifi background" />
<span
class="fa fa-wifi"
style={getWifiStrengthStyle(network)}
/>
</Graphic>
<Text style="margin-right: 20px;">{network.Name}</Text>
{#if network.Encryption !== "Open"}
<Meta>
<span class="material-symbols-outlined lock">lock</span>
<span class="fa fa-lock" />
</Meta>
{/if}
</Item>
@@ -188,8 +190,8 @@
span {
position: absolute;
width: 24px;
height: 24px;
top: 5px;
font-size: 22px;
&.background {
opacity: 0.25;

View File

@@ -1,5 +1,15 @@
<script lang="ts">
import * as api from "$lib/api";
import { onMount } from "svelte";
onMount(() => {
document.body.style.backgroundColor = "black";
const layout = document.querySelector("#layout") as HTMLElement;
layout.style.backgroundColor = "white";
layout.style.width = "1280px";
layout.style.height = "720px";
layout.style.overflowY = "scroll";
});
</script>
<div class="devmode">

View File

@@ -11,6 +11,7 @@
type Option = {
value: number;
label: string;
metric: boolean;
};
@@ -65,10 +66,7 @@
<List>
{#each options as option}
<Item on:SMUI:action={() => onOptionSelected(option)}>
<Text>
{option.value}
{option.metric ? "mm" : "in"}
</Text>
<Text>{option.label}</Text>
</Item>
{/each}
</List>
@@ -76,18 +74,18 @@
</div>
<style lang="scss">
.value-and-unit {
display: flex;
column-gap: 10px;
}
:global {
.mdc-select {
max-width: 60px;
}
.value-and-unit {
display: flex;
column-gap: 10px;
.smui-select--standard .mdc-select__dropdown-icon {
margin-left: 0;
.mdc-select {
max-width: 60px;
}
.smui-select--standard .mdc-select__dropdown-icon {
margin-left: 0;
}
}
}
</style>
</style>

View File

@@ -64,12 +64,12 @@
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="simple-title"
aria-describedby="simple-content"
aria-labelledby="change-hostname-dialog-title"
aria-describedby="change-hostname-dialog-content"
>
<Title id="simple-title">Change Hostname</Title>
<Title id="change-hostname-dialog-title">Change Hostname</Title>
<Content id="simple-content">
<Content id="change-hostname-dialog-content">
<TextField
bind:value={hostname}
label="New Hostname"
@@ -88,7 +88,7 @@
<Label>Cancel</Label>
</Button>
<Button defaultAction on:click={onConfirm} disabled={hostname.length === 0}>
<Label>Confirm</Label>
<Label>Confirm & Reboot</Label>
</Button>
</Actions>
</Dialog>

View File

@@ -1,31 +1,48 @@
<script lang="ts" context="module">
import { writable } from "svelte/store";
import HomeMachineDialog from "$dialogs/HomeMachineDialog.svelte";
import ProbeDialog from "$dialogs/ProbeDialog.svelte";
import {
HomeMachineProps,
ProbeProps,
type HomeMachinePropsType,
type ProbePropsType,
} from "$dialogs/DialogProps";
import ScreenRotationDialog from "$dialogs/ScreenRotationDialog.svelte";
const HomeMachineDialogProps = writable<HomeMachineDialogPropsType>();
type HomeMachineDialogPropsType = {
open: boolean;
home: () => void;
};
const ProbeDialogProps = writable<ProbeDialogPropsType>();
type ProbeDialogPropsType = {
open: boolean;
probeType: "xyz" | "z";
};
const ScreenRotationDialogProps = writable<ProbeDialogPropsType>();
type ScreenRotationDialogPropsType = {
open: boolean;
};
export function showDialog(
dialog: "HomeMachine",
props: Omit<HomeMachinePropsType, "open">
props: Omit<HomeMachineDialogPropsType, "open">
);
export function showDialog(
dialog: "Probe",
props: Omit<ProbePropsType, "open">
props: Omit<ProbeDialogPropsType, "open">
);
export function showDialog(dialog: string, props: any) {
switch (dialog) {
case "HomeMachine":
HomeMachineProps.set({ ...props, open: true });
HomeMachineDialogProps.set({ ...props, open: true });
break;
case "Probe":
ProbeProps.set({ ...props, open: true });
ProbeDialogProps.set({ ...props, open: true });
break;
case "ScreenRotation":
ScreenRotationDialogProps.set({ ...props, open: true });
break;
default:
@@ -34,5 +51,6 @@
}
</script>
<HomeMachineDialog {...$HomeMachineProps} />
<ProbeDialog {...$ProbeProps} />
<HomeMachineDialog {...$HomeMachineDialogProps} />
<ProbeDialog {...$ProbeDialogProps} />
<ScreenRotationDialog {...$ScreenRotationDialogProps} />

View File

@@ -1,13 +0,0 @@
import { writable } from "svelte/store";
export const HomeMachineProps = writable<HomeMachinePropsType>();
export type HomeMachinePropsType = {
open: boolean,
home: () => void
}
export const ProbeProps = writable<ProbePropsType>();
export type ProbePropsType = {
open: boolean,
probeType: "xyz" | "z"
};

View File

@@ -8,12 +8,12 @@
<Dialog
bind:open
aria-labelledby="simple-title"
aria-describedby="simple-content"
aria-labelledby="home-machine-dialog-title"
aria-describedby="home-machine-dialog-content"
>
<Title id="simple-title">Home Machine</Title>
<Title id="home-machine-dialog-title">Home Machine</Title>
<Content id="simple-content">Home the machine?</Content>
<Content id="home-machine-dialog-content">Home the machine?</Content>
<Actions>
<Button>

View File

@@ -9,11 +9,11 @@
bind:open
scrimClickAction=""
escapeKeyAction=""
aria-labelledby="simple-title"
aria-describedby="simple-content"
aria-labelledby="message-dialog-title"
aria-describedby="message-dialog-content"
>
<Title id="simple-title">{title}</Title>
<Content id="simple-content">
<Title id="message-dialog-title">{title}</Title>
<Content id="message-dialog-content">
<slot />
</Content>
</Dialog>

View File

@@ -63,28 +63,18 @@
import { Config } from "$lib/ConfigStore";
const cutterDiameterOptions = [
{ value: 0.5, metric: false },
{ value: 10, metric: true },
{ value: 0.25, metric: false },
{ value: 6, metric: true },
{ value: 0.125, metric: false },
{ value: 3, metric: true },
];
const cutterLengthOptions = [
{ value: 1, metric: false },
{ value: 20, metric: true },
{ value: 0.5, metric: false },
{ value: 10, metric: true },
{ value: 0.25, metric: false },
{ value: 6, metric: true },
{ 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 },
];
export let open;
export let probeType: "xyz" | "z";
let currentStep: Step = "None";
let cutterDiameter: number;
let cutterLength: number;
let showCancelButton = true;
let steps: Array<Step> = [];
let nextButton = {
@@ -98,8 +88,6 @@
$: if (open) {
cutterDiameter =
Number.parseFloat(localStorage.getItem("cutterDiameter")) || null;
cutterLength =
Number.parseFloat(localStorage.getItem("cutterLength")) || null;
// Svelte appears not to like it when you invoke
// an async function from a reactive statement, so we
@@ -107,7 +95,7 @@
requestAnimationFrame(begin);
}
$: if (cutterDiameter && cutterLength) {
$: if (cutterDiameter) {
updateButtons();
}
@@ -132,8 +120,7 @@
if (probeType === "xyz") {
await stepCompleted("BitDimensions", userAcknowledged);
localStorage.setItem("cutterDiameter", cutterDiameter);
localStorage.setItem("cutterLength", cutterLength);
localStorage.setItem("cutterDiameter", cutterDiameter.toString());
}
await stepCompleted("PlaceProbeBlock", userAcknowledged);
@@ -221,10 +208,7 @@
nextButton.disabled = !(
cutterDiameter !== null &&
cutterDiameter !== 0 &&
isFinite(cutterDiameter) &&
cutterLength !== null &&
cutterLength !== 0 &&
isFinite(cutterLength)
isFinite(cutterDiameter)
);
break;
@@ -246,6 +230,7 @@
const slowSeek = $Config.probe["probe-slow-seek"];
const fastSeek = $Config.probe["probe-fast-seek"];
const cutterLength = 12.7;
const zLift = 1;
const xOffset = probeBlockWidth + cutterDiameter / 2.0;
const yOffset = probeBlockLength + cutterDiameter / 2.0;
@@ -310,13 +295,13 @@
bind:open
class="probe-dialog"
scrimClickAction=""
aria-labelledby="simple-title"
aria-describedby="simple-content"
aria-labelledby="probe-dialog-title"
aria-describedby="probe-dialog-content"
surface$style="width: 700px; height: 400px; max-width: calc(100vw - 32px); overflow: visible;"
>
<Title id="simple-title">Probing {probeType?.toUpperCase()}</Title>
<Title id="probe-dialog-title">Probing {probeType?.toUpperCase()}</Title>
<Content id="simple-content" style="overflow: visible;">
<Content id="probe-dialog-content" style="overflow: visible;">
<div class="steps">
<p><b>Step {steps.indexOf(currentStep) + 1} of {steps.length}</b></p>
<ul>
@@ -337,12 +322,6 @@
bind:value={cutterDiameter}
{metric}
/>
<DimensionInput
label="Cutter length"
options={cutterLengthOptions}
bind:value={cutterLength}
{metric}
/>
{:else if currentStep === "PlaceProbeBlock"}
{#if probeType === "xyz"}
Place the probe block face up, on the lower-left corner of your
@@ -402,7 +381,7 @@
$light: #ddd;
:global {
.mdc-dialog__content {
#probe-dialog-content {
display: flex;
flex-direction: row;
}

View File

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

View File

@@ -18,8 +18,8 @@
$: needPassword = !network?.active && network?.Encryption !== "Open";
$: connectOrDisconnect = network?.active ? "Disconnect" : "Connect";
$: connectToOrDisconnectFrom = network?.active
? "Disconnect from"
: "Connect to";
? "Disconnect from"
: "Connect to";
$: if (open) {
password = "";
@@ -45,12 +45,14 @@
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="simple-title"
aria-describedby="simple-content"
aria-labelledby="wifi-connection-dialog-title"
aria-describedby="wifi-connection-dialog-content"
>
<Title id="simple-title">{connectToOrDisconnectFrom} {network.Name}</Title>
<Title id="wifi-connection-dialog-title"
>{connectToOrDisconnectFrom} {network.Name}</Title
>
<Content id="simple-content">
<Content id="wifi-connection-dialog-content">
{#if needPassword}
<TextField
bind:value={password}
@@ -64,9 +66,7 @@
slot="trailingIcon"
on:click={() => (showPassword = !showPassword)}
>
<Icon class="material-symbols-outlined">
{showPassword ? "password" : "abc"}
</Icon>
<Icon class={`fa ${showPassword ? "fa-eye-slash" : "fa-eye"}`} />
</div>
<HelperText persistent slot="helper">
Wifi passwords must be 8 to 128 characters
@@ -92,7 +92,7 @@
on:click={onConfirm}
disabled={needPassword && (password.length < 8 || password.length > 128)}
>
<Label>{connectOrDisconnect}</Label>
<Label>{connectOrDisconnect} & Reboot</Label>
</Button>
</Actions>
</Dialog>