diff --git a/src/js/a-axis-view.js b/src/js/a-axis-view.js new file mode 100644 index 0000000..132ec4d --- /dev/null +++ b/src/js/a-axis-view.js @@ -0,0 +1,20 @@ +"use strict"; + +// V09 A-axis page — mounts the AAxisSettings Svelte component +// inside the settings shell so it gets a real top-level rail entry +// instead of being a soft-link anchor inside Display & Units. + +module.exports = { + template: "#a-axis-view-template", + + attached: function () { + this.svelteComponent = SvelteComponents.createComponent( + "AAxisSettings", + document.getElementById("a-axis-mount") + ); + }, + + detached: function () { + if (this.svelteComponent) this.svelteComponent.$destroy(); + }, +}; diff --git a/src/js/app.js b/src/js/app.js index 8fa8c9f..f8d54f9 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -391,6 +391,7 @@ module.exports = new Vue({ "admin-general", "admin-network", "motor", "tool", "io", "macros", "help", "cheat-sheet", + "a-axis", ]; const initialHead = (location.hash || "").replace(/^#/, "").split(":")[0]; if (settingsFamily.indexOf(initialHead) === -1) { @@ -626,6 +627,7 @@ module.exports = new Vue({ "admin-general", "admin-network", "motor", "tool", "io", "macros", "help", "cheat-sheet", + "a-axis", ]; if (head == "control") { @@ -687,6 +689,13 @@ module.exports = new Vue({ try { await api.put("config/save", this.config); + // Notify any embedded Svelte subviews that own their + // own persistence (A axis -> aux.json, etc.) that + // the user just hit the master Save button. They + // listen for `onefin:save-all` and PUT their state. + try { + window.dispatchEvent(new CustomEvent("onefin:save-all")); + } catch (_e) {} this.modified = false; } catch (error) { console.error("Save failed:", error); diff --git a/src/js/control-view.js b/src/js/control-view.js index 4e928ac..6cbdf9f 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -249,13 +249,83 @@ module.exports = { api.put(`home/${axis}/clear`); }, + aux_home: function () { + api.put("aux/home").catch(function (err) { + console.error("Aux home failed:", err); + }); + }, + + // Home every enabled axis (legacy Onefinity "Home All"). Sequence: + // 1. Z, X, Y (and A/B/C if enabled) via /api/home on the AVR + // 2. Auxiliary axis via /api/aux/home on the ESP + // ONLY when the auxcnc axis is not integrated as a virtual + // machine axis. With the gplan A-axis integration (synthetic + // motor 4 enabled), Mach.home() already homes the external + // axis as part of the xyzabc pass - calling aux/home + // afterwards would home it a second time. + // /api/home returns as soon as the request is queued, not when + // homing completes, so we have to watch state.cycle: + // - first wait for it to *leave* 'idle' (cycle began), + // - then wait for it to come *back* to 'idle' (cycle ended). + // Only then do we fire the auxiliary home, so the gantry and the + // auxcnc ESP never move at the same time. home_all: async function () { this.ask_home = false; try { await api.put("home"); } catch (e) { - console.error("Home all failed:", e); + console.error("Home all (XYZ) failed:", e); + return; } + if (!this.w || !this.w.enabled) return; + + // When the synthetic external motor (index 4) is enabled, + // the auxcnc axis is mapped onto a real machine axis letter + // (e.g. A) and was already homed by /api/home above. + if (this.state && this.state["4me"]) return; + + const wait = (ms) => new Promise(r => setTimeout(r, ms)); + const cycleNow = () => (this.state && this.state.cycle) || "idle"; + + // Phase 1: wait up to 5s for the homing cycle to actually start. + // If the request was rejected upstream (e.g. estopped) cycle + // never leaves idle and we bail rather than home A in isolation. + const startedAt = Date.now(); + while (Date.now() - startedAt < 5000) { + if (cycleNow() != "idle") break; + await wait(100); + } + if (cycleNow() == "idle") { + console.warn("home_all: main homing cycle never started; skipping aux"); + return; + } + + // Phase 2: wait up to 2 minutes for the gantry to finish. + const settledAt = Date.now(); + while (Date.now() - settledAt < 120000) { + if (cycleNow() == "idle") break; + await wait(200); + } + if (cycleNow() != "idle") { + console.warn("home_all: gantry homing did not complete in time"); + return; + } + + api.put("aux/home").catch(function (err) { + console.error("Aux home failed:", err); + }); + }, + + aux_jog: function (delta_mm) { + api.put("aux/jog", { mm: delta_mm }).catch(function (err) { + console.error("Aux jog failed:", err); + }); + }, + + aux_jog_incr: function (sign) { + const amount = this.jog_incr_amounts[this.display_units][this.jog_incr]; + const delta_mm = sign * (this.metric ? amount : amount * 25.4); + this.aux_jog(delta_mm); }, show_set_position: function (axis) { diff --git a/src/js/settings-shell-view.js b/src/js/settings-shell-view.js index 1050a8b..299b9d6 100644 --- a/src/js/settings-shell-view.js +++ b/src/js/settings-shell-view.js @@ -24,6 +24,7 @@ module.exports = { "io-view": require("./io-view"), "macros-view": require("./macros"), "help-view": require("./help-view"), + "a-axis-view": require("./a-axis-view"), "cheat-sheet-view": { template: "#cheat-sheet-view-template", data: function () { @@ -56,6 +57,9 @@ module.exports = { { sub: "motor", motor: 1, href: "#motor:1", icon: "fa-arrows-up-down-left-right", label: "Motor 1" }, { sub: "motor", motor: 2, href: "#motor:2", icon: "fa-arrows-up-down-left-right", label: "Motor 2" }, { sub: "motor", motor: 3, href: "#motor:3", icon: "fa-arrows-up-down-left-right", label: "Motor 3" }, + // Auxiliary axis (auxcnc ESP32 - exposed to gplan as A). + // Mounts the AAxisSettings Svelte component on its own page. + { sub: "a-axis", href: "#a-axis", icon: "fa-arrows-up-down", label: "A Axis" }, { section: " " }, { sub: "help", href: "#help", icon: "fa-circle-question", label: "Help" }, ], @@ -133,6 +137,7 @@ module.exports = { // layout, which under tablet mode pulls the fixed header out // of view. if (location.hash !== item.href) location.hash = item.href; + this._a_axis_focus = (item.sub === "a-axis"); const reset = () => { // Force any inadvertent ancestor scroll back to 0 before // we move .settings-content explicitly. @@ -155,6 +160,7 @@ module.exports = { requestAnimationFrame(reset); }, 320); } else { + this._a_axis_focus = false; if (location.hash !== item.href) location.hash = item.href; // Reset .app-body scroll so each route starts at the top. const body = document.querySelector(".app-body"); diff --git a/src/pug/templates/a-axis-view.pug b/src/pug/templates/a-axis-view.pug new file mode 100644 index 0000000..2e61cc4 --- /dev/null +++ b/src/pug/templates/a-axis-view.pug @@ -0,0 +1,4 @@ +script#a-axis-view-template(type="text/x-template") + #a-axis-page + h1 A Axis (auxcnc) + #a-axis-mount diff --git a/src/pug/templates/control-view.pug b/src/pug/templates/control-view.pug index f250def..0e082e3 100644 --- a/src/pug/templates/control-view.pug +++ b/src/pug/templates/control-view.pug @@ -92,8 +92,33 @@ script#control-view-template(type="text/x-template") .fa.fa-arrow-down.ico(style="transform: rotate(-45deg)") button.jbtn(@click="jog_fn(0, 0, -1, 0)") Z− - // Row 4 — A axis (rotary) when rotary is enabled. - template(v-if="state['2an'] == 3") + // Row 4 — A axis (the auxcnc-driven external axis) when enabled. + // A- | A+ | Probe XYZ | Probe Z + // "Home A" lives in the DRO table's actions column on the + // right, so it doesn't need a tile here. The legacy w.enabled + // gate is kept so older installs (where the auxcnc axis still + // appears as W via the side-channel) keep working. + template(v-if="w.enabled || a.enabled") + button.jbtn(@click="aux_jog_incr(-1)", + :disabled="!(w.enabled || a.enabled)") + .fa.fa-arrow-down.ico + span.lbl A− + button.jbtn(@click="aux_jog_incr(+1)", + :disabled="!(w.enabled || a.enabled)") + .fa.fa-arrow-up.ico + span.lbl A+ + button.jbtn(@click="showProbeDialog('xyz')", + :class="{'load-on': !state['pw']}") + .fa.fa-bullseye.ico + span.lbl Probe XYZ + button.jbtn(@click="showProbeDialog('z')", + :class="{'load-on': !state['pw']}") + .fa.fa-bullseye.ico + span.lbl Probe Z + + // Row 4 — A axis (rotary) when no W and rotary is enabled + // (Vue 1 has no v-else-if; we negate w.enabled explicitly.) + template(v-if="!w.enabled && state['2an'] == 3") button.jbtn.dir(@click="jog_fn(0, 0, 0, -1)") .fa.fa-rotate-left.ico span.lbl A− @@ -109,7 +134,7 @@ script#control-view-template(type="text/x-template") span.lbl Probe // Row 4 — fallback probe / zero / home shortcuts - template(v-if="state['2an'] != 3") + template(v-if="!w.enabled && state['2an'] != 3") button.jbtn(@click="showProbeDialog('xyz')", :class="{'load-on': !state['pw']}") .fa.fa-bullseye.ico @@ -193,7 +218,8 @@ script#control-view-template(type="text/x-template") .actions-cell // Master Home All. Each row's Actions cell has a per-axis // home button; this header-level button homes every - // enabled axis (legacy Onefinity behavior). + // enabled axis (legacy Onefinity behavior). Auto-includes + // the auxiliary A axis when it is enabled. button.icon-btn(:disabled="!is_idle", title="Home all axes.", @click="home_all()") .fa.fa-house-chimney @@ -223,6 +249,28 @@ script#control-view-template(type="text/x-template") @click=`home('${axis}')`) .fa.fa-home + // Legacy auxiliary-axis row - shown only when the auxcnc stepper is + // *not* exposed as a virtual A axis. After v2 the standard + // A row above renders this axis natively (with full offset + // + set-position support); this row only appears on legacy + // installs that haven't migrated yet. + .dro-row(:class="w.klass + ' ' + w.tklass", + v-if="w.enabled && !a.enabled", + :title="w.title") + .dro-axis.axis-w W + .dro-pos: unit-value(:value="w.pos", precision=4) + .dro-sec: unit-value(:value="w.abs", precision=3) + .dro-sec — + .actions-cell + button.icon-btn(disabled, style="visibility:hidden") + .fa.fa-gear + button.icon-btn(disabled, style="visibility:hidden") + .fa.fa-location-dot + button.icon-btn(:class="w.homed ? 'state-green' : 'state-amber'", + :disabled="!w.enabled", + title="Home auxiliary axis.", @click="aux_home()") + .fa.fa-home + // ----- Status strip ----- .status-strip .stat-card diff --git a/src/pug/templates/settings-shell.pug b/src/pug/templates/settings-shell.pug index b7de0ff..a889b38 100644 --- a/src/pug/templates/settings-shell.pug +++ b/src/pug/templates/settings-shell.pug @@ -46,6 +46,8 @@ script#settings-shell-view-template(type="text/x-template") :index="index", :config="config", :template="template", :state="state") io-view(v-if="sub === 'io' && config_ready", :index="index", :config="config", :template="template", :state="state") + a-axis-view(v-if="sub === 'a-axis' && config_ready", + :index="index", :config="config", :template="template", :state="state") macros-view(v-if="sub === 'macros' && config_ready", :index="index", :config="config", :template="template", :state="state") help-view(v-if="sub === 'help' && config_ready", diff --git a/src/stylus/style.styl b/src/stylus/style.styl index 5915641..345a26a 100644 --- a/src/stylus/style.styl +++ b/src/stylus/style.styl @@ -1457,6 +1457,9 @@ tt.save .dro-axis.axis-c color #d946ef +.dro-axis.axis-w + color #7c3aed + .dro-pos font-family 'JetBrains Mono', monospace font-size 36px diff --git a/src/svelte-components/src/components/AAxisSettings.svelte b/src/svelte-components/src/components/AAxisSettings.svelte new file mode 100644 index 0000000..9c8c02f --- /dev/null +++ b/src/svelte-components/src/components/AAxisSettings.svelte @@ -0,0 +1,304 @@ + + +
+ {#if !cfg} +

Loading A axis configuration...

+ {:else} +
+ {#if status} + + Status: + {#if !status.enabled} + disabled + {:else if !status.present} + offline + {:else if status.homed} + homed at {status.pos_mm.toFixed(3)} mm + {:else} + connected, unhomed + {/if} + + {/if} +
+ +
+
+
+ + + +
+ +
+ + +
+ +
+ + +
+
+ +

Mechanics

+
+
+ + + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ +
+ + + +
+
+ +

Planner Limits

+
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+ +

Homing

+
+
+ + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + +
+
+ +

Step Profile

+
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+ +
+ Changes are written to aux.json when you click the + master Save button at the bottom of the + settings rail. Homing rates and the limit polarity are + pushed to the ESP immediately; any running motion is + unaffected. Re-home the auxiliary axis after changing direction, + sign, or step settings. +
+
+ {/if} +
+ + diff --git a/src/svelte-components/src/components/SettingsView.svelte b/src/svelte-components/src/components/SettingsView.svelte index 660b907..40c0012 100644 --- a/src/svelte-components/src/components/SettingsView.svelte +++ b/src/svelte-components/src/components/SettingsView.svelte @@ -2,6 +2,10 @@ import configTemplate from "../../../resources/config-template.json"; import ScreenRotationDialog from "$dialogs/ScreenRotationDialog.svelte"; import ConfigTemplatedInput from "./ConfigTemplatedInput.svelte"; + // AAxisSettings is mounted directly by the V09 settings shell at + // #a-axis instead of being embedded here — see + // src/pug/templates/a-axis-view.pug. + // import AAxisSettings from "./AAxisSettings.svelte"; import SetTimeDialog from "$dialogs/SetTimeDialog.svelte"; import Button, { Label } from "@smui/button"; @@ -94,6 +98,10 @@ {/each} + +

Path Accuracy

diff --git a/src/svelte-components/src/main.ts b/src/svelte-components/src/main.ts index 4fec558..cf077fa 100644 --- a/src/svelte-components/src/main.ts +++ b/src/svelte-components/src/main.ts @@ -6,6 +6,7 @@ matchAll.shim(); import AdminNetworkView from "$components/AdminNetworkView.svelte"; import SettingsView from "$components/SettingsView.svelte"; import HelpView from "$components/HelpView.svelte"; +import AAxisSettings from "$components/AAxisSettings.svelte"; import DialogHost, { showDialog } from "$dialogs/DialogHost.svelte"; import { handleConfigUpdate, setDisplayUnits } from "$lib/ConfigStore"; import { handleControllerStateUpdate } from "$lib/ControllerState"; @@ -22,6 +23,9 @@ export function createComponent(component: string, target: HTMLElement, props: R case "HelpView": return new HelpView({ target, props }); + case "AAxisSettings": + return new AAxisSettings({ target, props }); + case "DialogHost": return new DialogHost({ target, props });