Compare commits
4 Commits
549b69c234
...
1c69c0a157
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c69c0a157 | |||
|
|
748f092795 | ||
|
|
cfc14643d2 | ||
|
|
b0f38619ba |
@@ -255,19 +255,55 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Home every enabled axis at once (legacy Onefinity behavior).
|
// Home every enabled axis (legacy Onefinity "Home All"). Sequence:
|
||||||
// The XYZ home is fired first via the standard /api/home endpoint,
|
// 1. Z, X, Y (and A/B/C if enabled) via /api/home on the AVR
|
||||||
// then the W axis is homed if it is enabled. The two cycles run
|
// 2. W axis via /api/aux/home on the ESP
|
||||||
// in parallel — W is on the auxcnc ESP and doesn't share motors
|
// /api/home returns as soon as the request is queued, not when
|
||||||
// with the AVR — so the user sees one click homing everything.
|
// homing completes, so we have to watch state.cycle:
|
||||||
home_all: function () {
|
// - 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 W home, so the gantry and the auxcnc
|
||||||
|
// ESP never move at the same time.
|
||||||
|
home_all: async function () {
|
||||||
this.ask_home = false;
|
this.ask_home = false;
|
||||||
api.put("home");
|
try {
|
||||||
if (this.w && this.w.enabled) {
|
await api.put("home");
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Home all (XYZ) failed:", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.w || !this.w.enabled) 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 W 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 W");
|
||||||
|
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) {
|
api.put("aux/home").catch(function (err) {
|
||||||
console.error("W home failed:", err);
|
console.error("W home failed:", err);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
aux_jog: function (delta_mm) {
|
aux_jog: function (delta_mm) {
|
||||||
|
|||||||
@@ -79,17 +79,28 @@ module.exports = {
|
|||||||
|
|
||||||
// True only while a loaded G-code program is actually being
|
// True only while a loaded G-code program is actually being
|
||||||
// executed (running, paused/holding, or stopping). Excludes
|
// executed (running, paused/holding, or stopping). Excludes
|
||||||
// jogging, homing, MDI commands and other one-off motion that
|
// jogging, homing, probing, MDI commands and other one-off
|
||||||
// also leave state.cycle != 'idle' but should not bring up the
|
// motion that also leave state.xx == "RUNNING" but must not
|
||||||
// "Now Running" panel on the Control tab.
|
// swap the jog grid out for the "Now Running" panel.
|
||||||
|
//
|
||||||
|
// Distinguishing signal is state.cycle:
|
||||||
|
// - "idle" : nothing happening
|
||||||
|
// - "jogging" : user-initiated jog
|
||||||
|
// - "homing" : home cycle
|
||||||
|
// - "probing" : probe cycle
|
||||||
|
// - "mdi" : single MDI command
|
||||||
|
// - "running" : an actual loaded program is being run
|
||||||
|
// Only "running" (combined with a selected file) is what we want.
|
||||||
is_program_executing: function () {
|
is_program_executing: function () {
|
||||||
const xx = this.state && this.state.xx;
|
if (!this.state) return false;
|
||||||
if (xx == "RUNNING" || xx == "HOLDING" || xx == "STOPPING") {
|
const xx = this.state.xx;
|
||||||
// Only count it as a program run if a file is selected.
|
const cycle = this.state.cycle;
|
||||||
// Otherwise an MDI submission also reads xx=RUNNING.
|
const isExecState = xx == "RUNNING" || xx == "HOLDING" || xx == "STOPPING";
|
||||||
return !!(this.state && this.state.selected);
|
if (!isExecState) return false;
|
||||||
}
|
// The cycle string narrows it to a real program run; anything
|
||||||
return false;
|
// else (jogging / homing / probing / mdi) is a one-off.
|
||||||
|
if (cycle && cycle != "running" && cycle != "idle") return false;
|
||||||
|
return !!this.state.selected;
|
||||||
},
|
},
|
||||||
|
|
||||||
is_paused: function () {
|
is_paused: function () {
|
||||||
@@ -553,23 +564,32 @@ module.exports = {
|
|||||||
override_feed: function () { api.put(`override/feed/${this.feed_override}`); },
|
override_feed: function () { api.put(`override/feed/${this.feed_override}`); },
|
||||||
override_speed: function () { api.put(`override/speed/${this.speed_override}`); },
|
override_speed: function () { api.put(`override/speed/${this.speed_override}`); },
|
||||||
|
|
||||||
run_macro: function (id) {
|
run_macro: async function (id) {
|
||||||
if (this.state.macros[id].file_name == "default") {
|
if (this.state.macros[id].file_name == "default") {
|
||||||
this.showNoGcodeMessage = true;
|
this.showNoGcodeMessage = true;
|
||||||
} else {
|
return;
|
||||||
if (this.state.macros[id].file_name != this.state.selected) {
|
|
||||||
this.state.selected = this.state.macros[id].file_name;
|
|
||||||
}
|
}
|
||||||
|
const file_name = this.state.macros[id].file_name;
|
||||||
try {
|
try {
|
||||||
|
// Selecting a file on the server is a side effect of
|
||||||
|
// GET /api/file/<name>. The macro button used to mutate
|
||||||
|
// state.selected client-side and immediately call start, which
|
||||||
|
// raced the file fetch: if the server hadn't seen the new
|
||||||
|
// selection yet, mach.start() ran whichever file was selected
|
||||||
|
// last. Do it explicitly and await so start always sees the
|
||||||
|
// right file.
|
||||||
|
if (file_name != this.state.selected) {
|
||||||
|
this.state.selected = file_name;
|
||||||
|
await api.get(`file/${encodeURIComponent(file_name)}`);
|
||||||
|
}
|
||||||
this.load();
|
this.load();
|
||||||
if (this.state.macros[id].alert == true) {
|
if (this.state.macros[id].alert == true) {
|
||||||
this.macrosLoading = true;
|
this.macrosLoading = true;
|
||||||
} else {
|
} else {
|
||||||
setImmediate(() => this.start_pause());
|
await this.start_pause();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Error running program: ", error);
|
console.warn("Error running macro: ", error);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -196,12 +196,29 @@ class Planner():
|
|||||||
|
|
||||||
|
|
||||||
def _add_message(self, text):
|
def _add_message(self, text):
|
||||||
self.ctrl.state.add_message(text)
|
# HOOK:<event>:<data> messages are an internal IPC channel
|
||||||
|
# between the gcode preprocessor and Hooks; they should not
|
||||||
|
# surface as user-visible message popups. Hooks._on_state_change
|
||||||
|
# will still see them via the messages list before we filter.
|
||||||
line = self.ctrl.state.get('line', 0)
|
line = self.ctrl.state.get('line', 0)
|
||||||
if 0 <= line: where = '%s:%d' % (self.where, line)
|
if 0 <= line: where = '%s:%d' % (self.where, line)
|
||||||
else: where = self.where
|
else: where = self.where
|
||||||
|
|
||||||
|
if isinstance(text, str) and text.startswith('HOOK:'):
|
||||||
|
# Push it through state.messages so Hooks._on_state_change
|
||||||
|
# can see and dispatch it, then immediately ack it so the UI
|
||||||
|
# doesn't render a popup.
|
||||||
|
self.ctrl.state.add_message(text)
|
||||||
|
try:
|
||||||
|
msgs = self.ctrl.state.get('messages', []) or []
|
||||||
|
if msgs:
|
||||||
|
self.ctrl.state.ack_message(msgs[-1].get('id', -1))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.log.info('HOOK msg: %s' % text, where = where)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.ctrl.state.add_message(text)
|
||||||
self.log.message(text, where = where)
|
self.log.message(text, where = where)
|
||||||
|
|
||||||
|
|
||||||
@@ -369,6 +386,17 @@ class Planner():
|
|||||||
self.where = path
|
self.where = path
|
||||||
path = self.ctrl.get_path('upload', path)
|
path = self.ctrl.get_path('upload', path)
|
||||||
self.log.info('GCode:' + path)
|
self.log.info('GCode:' + path)
|
||||||
|
# Make sure W-axis tokens are rewritten before the planner sees
|
||||||
|
# the file. preprocess_file is a no-op for files without W and
|
||||||
|
# for files already rewritten (no W tokens remain after the
|
||||||
|
# first pass), so this is safe to run on every load.
|
||||||
|
try:
|
||||||
|
from bbctrl.AuxPreprocessor import preprocess_file
|
||||||
|
if preprocess_file(path, log = self.log):
|
||||||
|
self.log.info('Rewrote W-axis tokens in %s' % path)
|
||||||
|
except Exception:
|
||||||
|
self.log.exception('W-axis preprocess at load failed; '
|
||||||
|
'attempting to load file unchanged')
|
||||||
self._sync_position()
|
self._sync_position()
|
||||||
self.planner.load(path, self.get_config(False, True))
|
self.planner.load(path, self.get_config(False, True))
|
||||||
self.reset_times()
|
self.reset_times()
|
||||||
|
|||||||
Reference in New Issue
Block a user