Front-end side of the gplan-integrated A axis (B3).
- a-axis-view.{js,pug}: dedicated settings page that mounts the
AAxisSettings Svelte component and lives at #a-axis in the V09
settings rail.
- AAxisSettings.svelte: aux.json-backed form (axis letter, port,
homing direction, soft limits, ATC pin map, etc.) with master
Save integration via 'onefin:save-all'.
- main.ts + SettingsView.svelte: register AAxisSettings in the
Svelte component map; SettingsView no longer embeds the W axis
fieldset.
- settings-shell-view: 'A Axis' rail entry; route to a-axis-view.
- app.js: extend settings family to include 'a-axis'; broadcast
onefin:save-all from the master Save button.
- control-view: Home All button waits for the gantry cycle to
finish before firing Home A on a non-virtual setup; A jog
buttons; aux_jog/aux_home/aux_jog_incr methods.
- control-view.pug: A row in the DRO (with set-position + zero +
home actions), A- / A+ tiles in the jog grid (gated on
w.enabled || a.enabled), legacy W row kept for installs that
haven't migrated to the gplan integration.
- style.styl: dro-axis.axis-w color.
373 lines
11 KiB
JavaScript
373 lines
11 KiB
JavaScript
"use strict";
|
|
|
|
const api = require("./api");
|
|
const cookie = require("./cookie")("bbctrl-");
|
|
|
|
module.exports = {
|
|
template: "#control-view-template",
|
|
props: ["config", "template", "state"],
|
|
|
|
data: function () {
|
|
return {
|
|
current_time: "",
|
|
mach_units: this.$root.state.metric ? "METRIC" : "IMPERIAL",
|
|
axes: "xyzabc",
|
|
jog_incr_amounts: {
|
|
METRIC: {
|
|
fine: 0.1,
|
|
small: 1.0,
|
|
medium: 10,
|
|
large: 100,
|
|
},
|
|
IMPERIAL: {
|
|
fine: 0.005,
|
|
small: 0.05,
|
|
medium: 0.5,
|
|
large: 5,
|
|
},
|
|
},
|
|
jog_incr: localStorage.getItem("jog_incr") || "small",
|
|
jog_step: cookie.get_bool("jog-step"),
|
|
jog_adjust: parseInt(cookie.get("jog-adjust", 2)),
|
|
ask_home: true,
|
|
show_probe_dialog: false,
|
|
overrides_open: false,
|
|
};
|
|
},
|
|
|
|
components: {
|
|
"axis-control": require("./axis-control"),
|
|
},
|
|
|
|
watch: {
|
|
jog_incr: function (value) {
|
|
localStorage.setItem("jog_incr", value);
|
|
},
|
|
|
|
"state.metric": {
|
|
handler: function (metric) {
|
|
this.mach_units = metric ? "METRIC" : "IMPERIAL";
|
|
},
|
|
immediate: true,
|
|
},
|
|
|
|
jog_step: function () {
|
|
cookie.set_bool("jog-step", this.jog_step);
|
|
},
|
|
|
|
jog_adjust: function () {
|
|
cookie.set("jog-adjust", this.jog_adjust);
|
|
},
|
|
},
|
|
|
|
computed: {
|
|
display_units: {
|
|
cache: false,
|
|
get: function () {
|
|
return this.$root.display_units;
|
|
},
|
|
set: function (value) {
|
|
this.config.settings.units = value;
|
|
this.$root.display_units = value;
|
|
this.$dispatch("config-changed");
|
|
},
|
|
},
|
|
|
|
metric: function () {
|
|
return this.display_units === "METRIC";
|
|
},
|
|
|
|
mach_state: function () {
|
|
const cycle = this.state.cycle;
|
|
const state = this.state.xx;
|
|
|
|
if (state != "ESTOPPED" && (cycle == "jogging" || cycle == "homing")) {
|
|
return cycle.toUpperCase();
|
|
}
|
|
|
|
return state || "";
|
|
},
|
|
|
|
can_set_axis: function () {
|
|
return this.state.cycle == "idle";
|
|
},
|
|
|
|
is_idle: function () {
|
|
return this.state.cycle == "idle";
|
|
},
|
|
|
|
is_ready: function () {
|
|
return this.mach_state == "READY";
|
|
},
|
|
|
|
message: function () {
|
|
if (this.mach_state == "ESTOPPED") {
|
|
return this.state.er;
|
|
}
|
|
|
|
if (this.mach_state == "HOLDING") {
|
|
return this.state.pr;
|
|
}
|
|
|
|
if (this.state.messages.length) {
|
|
return this.state.messages.slice(-1)[0].text;
|
|
}
|
|
|
|
return "";
|
|
},
|
|
|
|
highlight_state: function () {
|
|
return this.mach_state == "ESTOPPED" || this.mach_state == "HOLDING";
|
|
},
|
|
|
|
plan_time: function () {
|
|
return this.state.plan_time;
|
|
},
|
|
|
|
plan_time_remaining: function () {
|
|
const stopping = this.mach_state == "STOPPING";
|
|
const running = this.mach_state == "RUNNING" || this.mach_state == "HOMING";
|
|
const holding = this.mach_state == "HOLDING";
|
|
if (!(stopping || running || holding)) return 0;
|
|
const tp = this.$root && this.$root.toolpath ? this.$root.toolpath.time : 0;
|
|
return (tp || 0) - this.plan_time;
|
|
},
|
|
|
|
state_kpi_class: function () {
|
|
const s = this.mach_state;
|
|
if (s == "ESTOPPED" || s == "FAULT" || s == "SHUTDOWN") return "bad";
|
|
if (s == "HOLDING" || s == "STOPPING") return "warn";
|
|
if (s == "RUNNING" || s == "HOMING" || s == "JOGGING") return "busy";
|
|
if (s == "READY") return "ok";
|
|
return "";
|
|
},
|
|
},
|
|
|
|
events: {
|
|
jog: function (axis, power) {
|
|
const data = { ts: new Date().getTime() };
|
|
data[axis] = power;
|
|
api.put("jog", data);
|
|
},
|
|
|
|
back2zero: function (axis0, axis1) {
|
|
this.send(`G0 ${axis0}0 ${axis1}0`);
|
|
},
|
|
|
|
step: function (axis, value) {
|
|
this.send(`
|
|
M70
|
|
G91
|
|
G0 ${axis}${value}
|
|
M72
|
|
`);
|
|
},
|
|
},
|
|
|
|
ready: function () {
|
|
setInterval(() => {
|
|
this.current_time = new Date().toLocaleTimeString();
|
|
}, 1000);
|
|
|
|
SvelteComponents.registerControllerMethods({
|
|
stop: (...args) => this.stop(...args),
|
|
send: (...args) => this.send(...args),
|
|
isAxisHomed: axis => this[axis].homed,
|
|
unhome: (...args) => this.unhome(...args),
|
|
set_position: (...args) => this.set_position(...args),
|
|
set_home: (...args) => this.set_home(...args),
|
|
});
|
|
},
|
|
|
|
methods: {
|
|
getJogIncrStyle(value) {
|
|
const weight = `font-weight:${this.jog_incr === value ? "bold" : "normal"}`;
|
|
const color = this.jog_incr === value ? "color:#0078e7" : "";
|
|
return [weight, color].join(";");
|
|
},
|
|
|
|
// Should the macro row render a colored left stripe for this
|
|
// macro? Only when the user has explicitly picked a color. The
|
|
// controller seeds new macros with default placeholders like
|
|
// "#ffffff" or "#dedede"; treat anything that close to white as
|
|
// "no color".
|
|
has_macro_color(macros) {
|
|
if (!macros || typeof macros.color !== "string") return false;
|
|
const c = macros.color.trim().toLowerCase();
|
|
if (!c) return false;
|
|
const defaults = [
|
|
"#fff", "#ffffff", "#fefefe", "#fdfdfd", "#fcfcfc",
|
|
"#dedede", "#dddddd", "#cccccc",
|
|
];
|
|
if (defaults.indexOf(c) !== -1) return false;
|
|
// Fallback: if the color is very close to white (sum of RGB
|
|
// > 690), suppress the stripe.
|
|
const m = c.match(/^#([0-9a-f]{6})$/);
|
|
if (m) {
|
|
const v = parseInt(m[1], 16);
|
|
const r = (v >> 16) & 0xff;
|
|
const g = (v >> 8) & 0xff;
|
|
const b = v & 0xff;
|
|
if (r + g + b > 690) return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
jog_fn: function (x_jog, y_jog, z_jog, a_jog) {
|
|
const amount = this.jog_incr_amounts[this.display_units][this.jog_incr];
|
|
|
|
const xcmd = `X${x_jog * amount}`;
|
|
const ycmd = `Y${y_jog * amount}`;
|
|
const zcmd = `Z${z_jog * amount}`;
|
|
const acmd = `A${a_jog * amount}`;
|
|
|
|
this.send(`
|
|
G91
|
|
${this.metric ? "G21" : "G20"}
|
|
G0 ${xcmd}${ycmd}${zcmd}${acmd}
|
|
`);
|
|
},
|
|
|
|
home: function (axis) {
|
|
this.ask_home = false;
|
|
|
|
if (typeof axis == "undefined") {
|
|
api.put("home");
|
|
} else if (this[axis].homingMode != "manual") {
|
|
if(this.state["2an"] == 3 && axis == "a") return;
|
|
api.put(`home/${axis}`);
|
|
} else {
|
|
SvelteComponents.showDialog("ManualHomeAxis", { axis });
|
|
}
|
|
},
|
|
|
|
set_home: function (axis, position) {
|
|
api.put(`home/${axis}/set`, { position: parseFloat(position) });
|
|
},
|
|
|
|
unhome: function (axis) {
|
|
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 (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) {
|
|
SvelteComponents.showDialog("SetAxisPosition", { axis });
|
|
},
|
|
|
|
showMoveToZeroDialog: function (axes) {
|
|
SvelteComponents.showDialog("MoveToZero", { axes });
|
|
},
|
|
|
|
showToolpathMessageDialog: function (axis) {
|
|
SvelteComponents.showDialog("Message", { title: this[axis].toolmsg });
|
|
},
|
|
|
|
set_position: function (axis, position) {
|
|
api.put(`position/${axis}`, { position: parseFloat(position) });
|
|
},
|
|
|
|
zero_all: function () {
|
|
for (const axis of "xyzabc") {
|
|
if (this[axis].enabled) {
|
|
this.zero(axis);
|
|
}
|
|
}
|
|
},
|
|
|
|
zero: function (axis) {
|
|
if (typeof axis == "undefined") this.zero_all();
|
|
else this.set_position(axis, 0);
|
|
},
|
|
|
|
showProbeDialog: function (probeType) {
|
|
if (this.show_probe_dialog) {
|
|
this.show_probe_dialog = false;
|
|
}
|
|
SvelteComponents.showDialog("Probe", {
|
|
probeType,
|
|
isRotaryActive: this.state["2an"] == 3,
|
|
});
|
|
},
|
|
},
|
|
|
|
mixins: [require("./program-mixin"), require("./axis-vars")],
|
|
};
|