"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")], };