Z-A coupling: auto-coordinate A on jogs and MDI
Match the file-preprocessor behaviour for live operator input. When a
Z-down jog or MDI line would push (A-Z) above the safe band, append
the matching A delta to the same line so the planner runs Z and A
together. Same direction-aware refusal: only error when the operator
explicitly asks A to move *up* (delta > 0) past the bound, or when
the required A would violate A's soft minimum.
Implementation:
* ExternalAxis.coordinate_mdi rewrites a multi-line MDI burst,
tracking G90/G91 modal across lines (jogs always emit
M70/G91/G0/M72; standard MDI defaults to G90). Z and A targets
are computed in machine coords using offset_z and offset_a so
the work-coord A token we emit is consistent with the operator's
frame.
* The 'A0' the jog UI emits for axes that aren't moving is treated
as 'no A intent' (G91 delta of zero) and freely overridden.
* Hooked into Mach.mdi after the existing ATC rewrite. On
ExternalAxisError the burst is dropped with a user message; the
planner check downstream still fires as defense in depth.
* Planner.__encode also catches ExternalAxisError now (vs
bricking on uncaught) - logs to the operator messages list and
halts the cycle cleanly so subsequent jogs work.
* check_coupling itself is now improvement-aware: only refuses
moves that worsen an existing violation. Pure XY jogs and
Z-up/A-down recovery moves pass even when (A-Z) is currently
above the bound.
Tested locally with synthetic MDI: small Z jog within band, Z jog
across the boundary (auto-injects A delta), G90 MDI G0 Z-50
(appends A106), explicit A-lift while Z deep (refuses), pure XY
jog (unchanged), G91 A-down (unchanged), G90 G0 A0 with
offset_a=134 (refuses as lift to home).
This commit is contained in:
@@ -272,13 +272,14 @@ class Planner():
|
||||
if type == 'line':
|
||||
# Z-A coupling check: every line block that touches Z (or
|
||||
# A) is validated against the projected (A,Z) machine
|
||||
# pair. The ExternalAxis check raises on violation so
|
||||
# gplan aborts the program with a useful error rather
|
||||
# than driving the gantry into the auxiliary tool. The
|
||||
# check is skipped when coupling is disabled or A isn't
|
||||
# homed (see ExternalAxis.check_coupling).
|
||||
# pair. The ExternalAxis check is improvement-aware: it
|
||||
# only refuses moves that worsen an existing violation
|
||||
# or push a healthy state into one. So pure-XY jogs and
|
||||
# recovery moves (Z up, A down) are not rejected even
|
||||
# when (A-Z) is currently above the bound.
|
||||
ext_check = getattr(self.ctrl, 'ext_axis', None)
|
||||
if ext_check is not None:
|
||||
from bbctrl.ExternalAxis import ExternalAxisError
|
||||
target = block.get('target') or {}
|
||||
z_target = target.get('z')
|
||||
if z_target is None: z_target = target.get('Z')
|
||||
@@ -287,9 +288,28 @@ class Planner():
|
||||
if a_target is None:
|
||||
a_target = target.get(a_letter.upper())
|
||||
if z_target is not None or a_target is not None:
|
||||
ext_check.check_coupling(
|
||||
target_a_machine=a_target,
|
||||
target_z_machine=z_target)
|
||||
try:
|
||||
ext_check.check_coupling(
|
||||
target_a_machine=a_target,
|
||||
target_z_machine=z_target)
|
||||
except ExternalAxisError as e:
|
||||
# Convert the raw error into a clean abort:
|
||||
# surface the message to the operator, stop
|
||||
# the cycle, and skip this block. Returning
|
||||
# None drops the block from the AVR queue;
|
||||
# mach.stop() halts further planner output
|
||||
# so the rest of an offending program can't
|
||||
# leak through. The planner stays usable
|
||||
# for new MDI / jog commands.
|
||||
self.log.warning('Z-A coupling refused: %s' % e)
|
||||
try:
|
||||
self.ctrl.state.add_message(
|
||||
'Z-A coupling refused move: ' + str(e))
|
||||
except Exception: pass
|
||||
try:
|
||||
self.ctrl.mach.stop()
|
||||
except Exception: pass
|
||||
return None
|
||||
|
||||
ext = self._external_axis_for_line(block)
|
||||
if ext is not None:
|
||||
|
||||
Reference in New Issue
Block a user