"use strict"; module.exports = { props: [ "state", "config" ], computed: { metric: function() { return this.$root.display_units === "METRIC"; }, x: function() { return this._compute_axis("x"); }, y: function() { return this._compute_axis("y"); }, z: function() { return this._compute_axis("z"); }, a: function() { return this._compute_axis("a"); }, b: function() { return this._compute_axis("b"); }, c: function() { return this._compute_axis("c"); }, w: function() { return this._compute_aux_axis(); }, axes: function() { return this._compute_axes(); } }, methods: { _convert_length: function(value) { return this.metric ? value : value / 25.4; }, _length_str: function(value) { return this._convert_length(value).toLocaleString() + (this.metric ? " mm" : " in"); }, _compute_axis: function(axis) { const abs = this.state[`${axis}p`] || 0; const off = this.state[`offset_${axis}`]; const motor_id = this._get_motor_id(axis); // motor_id may be 4 for the synthetic external-axis motor; // there is no entry for it in config.motors so guard with // an empty object to avoid undefined property access. const motor = (motor_id == -1 ? {} : (this.config.motors[motor_id] || {})); const enabled = this._check_is_enabled(axis); const homingMode = motor["homing-mode"]; const homed = this.state[`${motor_id}homed`]; const min = this.state[`${motor_id}tn`]; const max = this.state[`${motor_id}tm`]; const dim = max - min; const pathMin = this.state[`path_min_${axis}`]; const pathMax = this.state[`path_max_${axis}`]; const pathDim = pathMax - pathMin; const under = pathMin + off < min; const over = max < pathMax + off; let klass = `${homed ? "homed" : "unhomed"} axis-${axis}`; let state = "UNHOMED"; let icon = "question-circle"; const fault = this.state[`${motor_id}df`] & 0x1f; const shutdown = this.state.power_shutdown; let title; let ticon = "question-circle"; let tstate = "NO FILE"; let toolmsg; let tklass = `${homed ? "homed" : "unhomed"} axis-${axis}`; if (fault || shutdown) { state = shutdown ? "SHUTDOWN" : "FAULT"; klass += " error"; icon = "exclamation-circle"; } else if (homed) { state = "HOMED"; icon = "check-circle"; } if (0 < dim && dim < pathDim) { tstate = "NO FIT"; tklass += " error"; ticon = "ban"; } else { if (over || under) { tstate = over ? "OVER" : "UNDER"; tklass += " warn"; ticon = "exclamation-circle"; } else { tstate = "OK"; ticon = "check-circle"; } } switch (state) { case "UNHOMED": title = "Click the home button to home axis."; break; case "HOMED": title = "Axis successfuly homed."; break; case "FAULT": title = [ `Motor driver fault. A potentially damaging electrical`, `condition was detected and the motor driver was shutdown.`, `Please power down the controller and check your motor cabling.`, `See the "Motor Faults" table on the "Indicators" tab for more`, `information.`, ].join(" "); break; case "SHUTDOWN": title = [ `Motor power fault. All motors in shutdown.`, `See the "Power Faults" table on the "Indicators" tab for more`, `information. Reboot controller to reset.` ].join(" "); break; } switch (tstate) { case "OVER": toolmsg = [ `Caution: The current tool path file would move`, `${this._length_str(pathMax + off - max)}`, `above axis limit with the current offset.` ].join(" "); break; case "UNDER": toolmsg = [ `Caution: The current tool path file would move`, `${this._length_str(min - pathMin - off)}`, `below limit with the current offset.` ].join(" "); break; case "NO FIT": toolmsg = [ `Warning: The current tool path dimensions`, `(${this._length_str(pathDim)}) exceed axis dimensions`, `(${this._length_str(dim)}) by ${this._length_str(pathDim - dim)}.` ].join(" "); break; default: toolmsg = `Tool path ${axis} dimensions OK.`; break; } return { pos: Math.abs(abs - off) < 0.00001 ? 0 : abs - off, abs: abs, off: off, min: min, max: max, dim: dim, pathMin: pathMin, pathMax: pathMax, pathDim: pathDim, motor: motor_id, enabled: enabled, homingMode: homingMode, homed: homed, klass: klass, state: state, icon: icon, title: title, ticon: ticon, tstate: tstate, toolmsg: toolmsg, tklass: tklass }; }, _get_motor_id: function(axis) { for (let i = 0; i < this.config.motors.length; i++) { const motor = this.config.motors[i]; // motor.axis can be undefined on initial load before // config has streamed in. Guard so the computed does // not throw and bubble a Vue warning into the console. if (motor && typeof motor.axis === "string" && motor.axis.toLowerCase() == axis) { return i; } } // Synthetic external motor (index 4) used by ExternalAxis // to expose the auxcnc ESP stepper as a virtual axis. // Its `Nan` lives in state, not config. const axes = { x: 0, y: 1, z: 2, a: 3, b: 4, c: 5 }; const wanted = axes[axis]; const extAn = this.state && this.state["4an"]; if (typeof wanted === "number" && typeof extAn === "number" && extAn === wanted) { return 4; } return -1; }, _check_is_enabled: function(axis){ // Prefer config.motors[i].axis (always present once the // config has loaded). Fall back to the per-motor state // `Nan` field, which is what the legacy UI used. This // avoids hiding axis rows during the brief window after // config has loaded but before the controller has pushed // its first state delta. const axes = { x: 0, y: 1, z: 2, a: 3 }; const wanted = axes[axis]; for (let i = 0; i < this.config.motors.length; i++) { const motor = this.config.motors[i] || {}; if (typeof motor.axis === "string" && motor.axis.toLowerCase() == axis) { return motor.enabled !== false; } // Only use the state Nan fallback for axes we know // about (x/y/z/a). Otherwise undefined == undefined // would mistakenly match every axis (b, c, ...). if (typeof wanted === "number") { const an = this.state[`${i}an`]; if (typeof an === "number" && an === wanted) { return true; } } } // Synthetic external motor (index 4) - the auxcnc ESP // stepper exposed as A via ExternalAxis. if (typeof wanted === "number") { const extAn = this.state["4an"]; const extMe = this.state["4me"]; if (typeof extAn === "number" && extAn === wanted && extMe) { return true; } } return false; }, _compute_aux_axis: function() { // Auxiliary axis driven by the auxcnc ESP32 (typically // exposed to gplan as A). Position, homed flag and // presence come from the bbctrl AuxAxis driver via // state.aux_*. No motor mapping, no soft-limit warnings // on toolpath bounds (auxcnc enforces its own). const enabled = !!this.state.aux_enabled; const present = !!this.state.aux_present; const homed = !!this.state.aux_homed; const pos = this.state.aux_pos || 0; let klass = `${homed ? "homed" : "unhomed"} axis-w`; let state = present ? "UNHOMED" : "OFFLINE"; let icon = present ? "question-circle" : "plug"; let title = present ? "Click the home button to home the auxiliary axis." : "Aux controller not connected on /dev/ttyUSB0."; if (homed) { state = "HOMED"; icon = "check-circle"; title = "Auxiliary axis successfully homed."; } else if (!present) { klass += " error"; } return { pos: pos, abs: pos, off: 0, min: 0, max: 0, dim: 0, pathMin: 0, pathMax: 0, pathDim: 0, motor: -1, enabled: enabled, homingMode: "limit-switch", homed: homed, klass: klass, state: state, icon: icon, title: title, ticon: "check-circle", tstate: "OK", toolmsg: "Auxiliary axis is not constrained by tool path bounds.", tklass: `${homed ? "homed" : "unhomed"} axis-w`, isAux: true, }; }, _compute_axes: function() { let homed = false; for (const name of "xyzabc") { const axis = this[name]; if (!axis.enabled) { continue; } if (!axis.homed) { homed = false; break; } homed = true; } let error = false; let warn = false; if (homed) { for (const name of "xyzabc") { const axis = this[name]; if (!axis.enabled) { continue; } if (axis.klass.indexOf("error") != -1) { error = true; } if (axis.klass.indexOf("warn") != -1) { warn = true; } } } let klass = homed ? "homed" : "unhomed"; if (error) { klass += " error"; } else if (warn) { klass += " warn"; } if (!homed && this.ask_home) { this.ask_home = false; SvelteComponents.showDialog("HomeMachine", { home: () => this.home() }); } return { homed: homed, klass: klass }; } } };