Rename W axis -> A axis everywhere (with migration)

Auxiliary axis is the auxcnc-driven stepper exposed to gplan as A,
not W. Half the stack already used A (gcode, DRO row, soft limits,
homing); the other half (settings tab, macros, internal field
names) still said W which was confusing.

Renames:
  - aux.json fields:       min_w/max_w        -> min_mm/max_mm
  - svelte component:      WAxisSettings      -> AAxisSettings
  - settings tab slug:     #w-axis            -> #a-axis
  - js view module:        w-axis-view.js     -> a-axis-view.js
  - pug template:          w-axis-view.pug    -> a-axis-view.pug
  - macros:                w_down.nc/w_up.nc  -> a_down.nc/a_up.nc
                           'W Down'/'W Up'    -> 'A Down'/'A Up'
  - css class & ids:       .w-axis-settings   -> .a-axis-settings,
                           min_w/max_w form ids match field names
  - internal js identifiers and comments

Migration:
  - AuxAxis._migrate_legacy_fields() promotes min_w/max_w in aux.json
    on every load and persists the upgraded form, so existing
    installs come out clean on first restart.
  - Config._upgrade() renames macro file_name and display name in
    every config.json load, so a stale in-memory copy can't
    reintroduce the W names. Ships with a save right after.

The auxcnc ESP wire protocol verbs (WPOS/HOMED) are unchanged - they
are wire-format identifiers, not user-facing labels.
This commit is contained in:
2026-05-03 13:37:16 +02:00
parent c4c20c6d0a
commit 0493a4ddc7
13 changed files with 112 additions and 50 deletions

View File

@@ -1,16 +1,16 @@
"use strict";
// V09 W-axis page \u2014 mounts the existing WAxisSettings Svelte component
// 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: "#w-axis-view-template",
template: "#a-axis-view-template",
attached: function () {
this.svelteComponent = SvelteComponents.createComponent(
"WAxisSettings",
document.getElementById("w-axis-mount")
"AAxisSettings",
document.getElementById("a-axis-mount")
);
},

View File

@@ -369,7 +369,7 @@ module.exports = new Vue({
ready: function() {
window.onhashchange = () => this.parse_hash();
// Embedded Svelte subviews (W axis settings, etc.) signal
// Embedded Svelte subviews (A axis settings, etc.) signal
// unsaved changes via this event. The master Save button
// highlights when modified is true.
window.addEventListener("onefin:dirty", () => {
@@ -391,7 +391,7 @@ module.exports = new Vue({
"admin-general", "admin-network",
"motor", "tool", "io", "macros",
"help", "cheat-sheet",
"w-axis",
"a-axis",
];
const initialHead = (location.hash || "").replace(/^#/, "").split(":")[0];
if (settingsFamily.indexOf(initialHead) === -1) {
@@ -627,7 +627,7 @@ module.exports = new Vue({
"admin-general", "admin-network",
"motor", "tool", "io", "macros",
"help", "cheat-sheet",
"w-axis",
"a-axis",
];
if (head == "control") {
@@ -690,7 +690,7 @@ module.exports = new Vue({
try {
await api.put("config/save", this.config);
// Notify any embedded Svelte subviews that own their
// own persistence (W axis -> aux.json, etc.) that
// 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 {

View File

@@ -24,7 +24,7 @@ module.exports = {
"io-view": require("./io-view"),
"macros-view": require("./macros"),
"help-view": require("./help-view"),
"w-axis-view": require("./w-axis-view"),
"a-axis-view": require("./a-axis-view"),
"cheat-sheet-view": {
template: "#cheat-sheet-view-template",
data: function () {
@@ -58,10 +58,8 @@ module.exports = {
{ 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 WAxisSettings Svelte component on its own page.
// The route slug stays "#w-axis" for back-compat with any
// bookmarks; the visible label and tab content say "A Axis".
{ sub: "w-axis", href: "#w-axis", icon: "fa-arrows-up-down", label: "A Axis" },
// 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" },
],
@@ -139,7 +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._w_axis_focus = (item.sub === "w-axis");
this._a_axis_focus = (item.sub === "a-axis");
const reset = () => {
// Force any inadvertent ancestor scroll back to 0 before
// we move .settings-content explicitly.
@@ -162,7 +160,7 @@ module.exports = {
requestAnimationFrame(reset);
}, 320);
} else {
this._w_axis_focus = false;
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");

View File

@@ -0,0 +1,4 @@
script#a-axis-view-template(type="text/x-template")
#a-axis-page
h1 A Axis (auxcnc)
#a-axis-mount

View File

@@ -249,7 +249,7 @@ script#control-view-template(type="text/x-template")
@click=`home('${axis}')`)
.fa.fa-home
// Legacy W axis row - shown only when the auxcnc stepper is
// 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

View File

@@ -46,7 +46,7 @@ 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")
w-axis-view(v-if="sub === 'w-axis' && config_ready",
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")

View File

@@ -1,4 +0,0 @@
script#w-axis-view-template(type="text/x-template")
#w-axis-page
h1 W Axis (auxcnc)
#w-axis-mount

View File

@@ -40,8 +40,8 @@ DEFAULTS = {
# 4th axis). gcode uses A for moves; the host ExternalAxis layer
# forks A motion to the ESP transparently.
'axis_letter': 'a',
'min_w': 0.0, # soft limit min (mm), exposed as 4tn
'max_w': 100.0, # soft limit max (mm), exposed as 4tm
'min_mm': 0.0, # soft limit min (mm), exposed as 4tn
'max_mm': 100.0, # soft limit max (mm), exposed as 4tm
# Per-axis kinematic limits used to populate the planner's config.
# Units match the bbctrl/onefinity per-motor convention so the
# values are directly comparable to motors 0-3:
@@ -120,23 +120,61 @@ class AuxAxis(object):
def _config_path(self):
return self.ctrl.get_path(filename='aux.json')
# Legacy aux.json fields that have been renamed for clarity.
# Loaded values are migrated up on every load/save so existing
# installs keep working without operator intervention.
_LEGACY_FIELD_MAP = {
'min_w': 'min_mm',
'max_w': 'max_mm',
}
def _migrate_legacy_fields(self, cfg):
"""In-place rename of legacy keys in `cfg` (dict). Returns
True if anything was migrated, so callers can decide whether
to persist the upgraded form.
"""
migrated = False
for old, new in self._LEGACY_FIELD_MAP.items():
if old in cfg:
if new not in cfg:
cfg[new] = cfg[old]
del cfg[old]
migrated = True
return migrated
def _load_config(self):
path = self._config_path()
if os.path.exists(path):
try:
with open(path) as f:
user = json.load(f)
migrated = self._migrate_legacy_fields(user)
# Be permissive; ignore unknown keys.
for k, v in user.items():
if k in self._cfg:
self._cfg[k] = v
self.log.info('Loaded aux config from %s' % path)
if migrated:
# Persist the upgraded form so future restarts
# see the new field names directly.
try:
self.save_config(self._cfg)
self.log.info(
'Migrated aux.json legacy fields '
'(min_w/max_w -> min_mm/max_mm)')
except Exception:
self.log.warning(
'Could not persist aux.json migration')
except Exception:
self.log.error('Failed to read aux.json: %s'
% traceback.format_exc())
def save_config(self, cfg):
merged = dict(DEFAULTS)
# Accept legacy keys from callers that may still send the
# old names (older UI bundles, hand-edited POSTs).
cfg = dict(cfg)
self._migrate_legacy_fields(cfg)
for k, v in cfg.items():
if k in DEFAULTS:
merged[k] = v
@@ -317,8 +355,8 @@ class AuxAxis(object):
raise AuxAxisError('Aux axis not connected')
def _check_limits(self, target_mm):
lo = float(self._cfg['min_w'])
hi = float(self._cfg['max_w'])
lo = float(self._cfg['min_mm'])
hi = float(self._cfg['max_mm'])
if hi <= lo:
return # no limits
if target_mm < lo - 1e-6 or target_mm > hi + 1e-6:

View File

@@ -216,6 +216,32 @@ class Config(object):
defaults = json.load(f)
config['selected-tool-settings'] = defaults['selected-tool-settings'];
# Auxiliary axis nomenclature: rename W -> A in macro names and
# filenames. The auxcnc-driven stepper has been integrated into
# gplan as A since the option-b migration; old configs may
# still carry W Down/W Up macro entries pointing at
# w_down.nc/w_up.nc which were renamed on disk to a_down.nc /
# a_up.nc. Migrate idempotently on every load so a stale
# in-memory copy can never reintroduce the old names.
macros = config.get('macros') if isinstance(config, dict) else None
if isinstance(macros, list):
renames = {
'w_down.nc': 'a_down.nc',
'w_up.nc': 'a_up.nc',
}
display_renames = {
'W Down': 'A Down',
'W Up': 'A Up',
}
for m in macros:
if not isinstance(m, dict): continue
fn = m.get('file_name')
if isinstance(fn, str) and fn in renames:
m['file_name'] = renames[fn]
nm = m.get('name')
if isinstance(nm, str) and nm in display_renames:
m['name'] = display_renames[nm]
config['version'] = self.version.split('b')[0]
config['full_version'] = self.version

View File

@@ -146,8 +146,8 @@ class ExternalAxis(object):
"""Return (min_mm, max_mm) in machine coords, or (None, None)
if soft limits are disabled (max <= min)."""
try:
lo = float(self.aux._cfg.get('min_w', 0.0))
hi = float(self.aux._cfg.get('max_w', 0.0))
lo = float(self.aux._cfg.get('min_mm', 0.0))
hi = float(self.aux._cfg.get('max_mm', 0.0))
except Exception:
return (None, None)
if hi <= lo:
@@ -230,8 +230,8 @@ class ExternalAxis(object):
# Soft limits in machine units (mm). State.get_soft_limit_vector
# returns these directly, no scaling.
st.set(i + 'tn', float(cfg.get('min_w', 0.0)))
st.set(i + 'tm', float(cfg.get('max_w', 0.0)))
st.set(i + 'tn', float(cfg.get('min_mm', 0.0)))
st.set(i + 'tm', float(cfg.get('max_mm', 0.0)))
# home_position / home_travel are exposed as callbacks for
# motors 0..3 (see State.__init__). Register the same lazy
@@ -242,7 +242,7 @@ class ExternalAxis(object):
i + 'home_position', lambda name: self.home_position_mm)
st.set_callback(
i + 'home_travel',
lambda name: float(self.aux._cfg.get('max_w', 0.0))
lambda name: float(self.aux._cfg.get('max_mm', 0.0))
- self.home_position_mm)
# Misc fields that other code paths might query. Defaults

View File

@@ -6,9 +6,9 @@
// Mirrors the DEFAULTS in src/py/bbctrl/AuxAxis.py. The "enabled"
// flag is read-only here; toggling the auxiliary A axis on/off
// is done via aux.json on disk, so adding/removing the hardware
// doesn't have a surprise UI that bricks bring-up. Field names
// (min_w/max_w) keep the historical "w" prefix for back-compat
// with existing aux.json files - rename happens at display only.
// doesn't have a surprise UI that bricks bring-up. Legacy aux.json
// files using min_w/max_w are migrated up to min_mm/max_mm by
// AuxAxis._migrate_legacy_fields on load.
type AuxConfig = {
enabled: boolean;
port: string;
@@ -16,8 +16,8 @@
steps_per_mm: number;
dir_sign: number;
axis_letter: string;
min_w: number;
max_w: number;
min_mm: number;
max_mm: number;
max_feed_mm_min: number;
max_velocity_m_per_min: number;
max_accel_km_per_min2: number;
@@ -88,9 +88,9 @@
}
</script>
<div class="w-axis-settings">
<div class="a-axis-settings">
{#if !cfg}
<p class="tip">Loading auxiliary A axis configuration...</p>
<p class="tip">Loading A axis configuration...</p>
{:else}
<div class="status">
{#if status}
@@ -154,14 +154,14 @@
</div>
<div class="pure-control-group" title="Soft-limit minimum in mm.">
<label for="min_w">soft min</label>
<input id="min_w" type="number" bind:value={cfg.min_w} step="any" />
<label for="min_mm">soft min</label>
<input id="min_mm" type="number" bind:value={cfg.min_mm} step="any" />
<label for="" class="units">mm</label>
</div>
<div class="pure-control-group" title="Soft-limit maximum in mm.">
<label for="max_w">soft max</label>
<input id="max_w" type="number" bind:value={cfg.max_w} step="any" />
<label for="max_mm">soft max</label>
<input id="max_mm" type="number" bind:value={cfg.max_mm} step="any" />
<label for="" class="units">mm</label>
</div>
</fieldset>
@@ -273,7 +273,7 @@
</div>
<style lang="scss">
.w-axis-settings {
.a-axis-settings {
.status {
margin-bottom: 1em;
font-size: 90%;

View File

@@ -2,10 +2,10 @@
import configTemplate from "../../../resources/config-template.json";
import ScreenRotationDialog from "$dialogs/ScreenRotationDialog.svelte";
import ConfigTemplatedInput from "./ConfigTemplatedInput.svelte";
// WAxisSettings is mounted directly by the V09 settings shell at
// #w-axis instead of being embedded here — see
// src/pug/templates/w-axis-view.pug.
// import WAxisSettings from "./WAxisSettings.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";
@@ -99,7 +99,7 @@
</fieldset>
<!-- W Axis (auxcnc) is now its own routed page in the V09
settings shell (#w-axis). Keep the SettingsView free of
settings shell (#a-axis). Keep the SettingsView free of
that section so we don't render it twice. -->
<h2 id="sec-path-accuracy" data-sec="gcode">Path Accuracy</h2>

View File

@@ -6,7 +6,7 @@ matchAll.shim();
import AdminNetworkView from "$components/AdminNetworkView.svelte";
import SettingsView from "$components/SettingsView.svelte";
import HelpView from "$components/HelpView.svelte";
import WAxisSettings from "$components/WAxisSettings.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";
@@ -23,8 +23,8 @@ export function createComponent(component: string, target: HTMLElement, props: R
case "HelpView":
return new HelpView({ target, props });
case "WAxisSettings":
return new WAxisSettings({ target, props });
case "AAxisSettings":
return new AAxisSettings({ target, props });
case "DialogHost":
return new DialogHost({ target, props });