Macros: fix wrong-file race, suppress HOOK message popups, preprocess at load
Three fixes for macro/W-axis interaction:
1. run_macro raced the file selection. The frontend mutated
state.selected client-side and immediately fired api.put('start').
Selection on the server is a side effect of GET /api/file/<name>
(FileHandler.get calls state.select_file). The GET request was
often still in flight when start ran, so mach.start() executed
whichever file was selected last - pressing W Down would re-run
W Up. Now run_macro awaits the file fetch before starting.
2. (MSG,HOOK:aux:N) lines, used as the IPC channel between the
W-axis preprocessor and the Hooks system, were leaking to the
user as message popups (because the planner forwards every
(MSG,...) comment to state.messages). Filter HOOK: messages in
Planner._add_message: still pushed through state.messages so
Hooks._on_state_change can dispatch them, but immediately
acked so the UI doesn't render them.
3. AuxPreprocessor only ran at upload time (FileHandler.put_ok and
Mach.mdi). Files written via scp, restored from a config backup,
or hand-edited still contained raw W tokens that the planner
couldn't parse. Run preprocess_file in Planner.load() too. It's
idempotent (no-op when no W tokens remain) so re-loading a
already-rewritten file is free.
This commit is contained in:
@@ -564,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