ExternalAxis: enforce soft limits in execute_to_mm and enqueue_target_mm
Soft limits in machine coords (min_w/max_w from aux.json) were only checked by gplan. UI jog/move endpoints went through ExternalAxis directly without any check, so the W+ button at home would happily push past max_w into a physical crash. Add _check_soft_limit(target_abs_mm) called by both motion paths: the synchronous execute_to_mm (UI) and the non-blocking enqueue_target_mm (planner). Boundaries inclusive within a 1e-4 epsilon for floating-point round-trip stability. Skipped when the axis isn't homed, matching the standard bbctrl convention that soft limits are gated by homing state. Skipped when max <= min (disabled). Tested locally: - pre-home: 200mm allowed (jog-out-of-trouble path) - post-home: 0 and 134 (boundaries) accepted - post-home: 135 and -1 rejected with clear error - 134.00005 accepted (within epsilon), 134.001 rejected - enqueue path also rejects, propagating up through Planner.next() - max==min config skips check
This commit is contained in:
@@ -140,6 +140,51 @@ class ExternalAxis(object):
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
# ------------------------------------------------------- soft limits
|
||||
|
||||
def _soft_limits(self):
|
||||
"""Return (min_mm, max_mm) in machine coords, or (None, None)
|
||||
if soft limits are disabled (max <= min)."""
|
||||
try:
|
||||
lo = float(self.aux._cfg.get('min_w', 0.0))
|
||||
hi = float(self.aux._cfg.get('max_w', 0.0))
|
||||
except Exception:
|
||||
return (None, None)
|
||||
if hi <= lo:
|
||||
return (None, None)
|
||||
return (lo, hi)
|
||||
|
||||
def _check_soft_limit(self, target_abs_mm):
|
||||
"""Raise ExternalAxisError if target_abs_mm is outside the
|
||||
configured soft limits. Skips the check when the axis isn't
|
||||
homed (matching the standard bbctrl convention that soft
|
||||
limits are gated by homing state) - that lets the user jog
|
||||
away from a stuck position before homing without false
|
||||
rejections.
|
||||
|
||||
Called by both planner-driven motion (enqueue_target_mm) and
|
||||
UI motion (execute_to_mm), so this is the single source of
|
||||
truth regardless of which path triggered the move."""
|
||||
# Honour the homing gate.
|
||||
try:
|
||||
homed = bool(self.aux._homed)
|
||||
except Exception:
|
||||
homed = False
|
||||
if not homed:
|
||||
return
|
||||
lo, hi = self._soft_limits()
|
||||
if lo is None:
|
||||
return
|
||||
# Use a tiny epsilon so floating-point round-trip targets
|
||||
# right at the boundary aren't rejected.
|
||||
eps = 1e-4
|
||||
target = float(target_abs_mm)
|
||||
if target < lo - eps or target > hi + eps:
|
||||
raise ExternalAxisError(
|
||||
'%s axis target %.4f mm is outside soft limits '
|
||||
'[%.3f, %.3f] mm' % (
|
||||
self.axis_letter.upper(), target, lo, hi))
|
||||
|
||||
# ----------------------------------------------------------- conversion
|
||||
|
||||
def mm_to_steps_delta(self, delta_mm):
|
||||
@@ -267,6 +312,9 @@ class ExternalAxis(object):
|
||||
endpoints which may want to wait. Most planner-driven motion
|
||||
goes through enqueue_target_mm instead, which is non-blocking.
|
||||
|
||||
Soft limits are enforced here (not just in gplan) because the
|
||||
UI jog/move endpoints don't go through the planner.
|
||||
|
||||
Updates state.<axis>p immediately on completion. For the
|
||||
planner-driven path that goes through enqueue_target_mm, the
|
||||
AVR's own ap reports drive state.<axis>p instead."""
|
||||
@@ -275,6 +323,7 @@ class ExternalAxis(object):
|
||||
'External axis %r not available (aux disabled or '
|
||||
'not connected)' % self.axis_letter)
|
||||
|
||||
self._check_soft_limit(ext_mm)
|
||||
steps, abs_mm = self._compute_move(ext_mm)
|
||||
if steps == 0:
|
||||
self._pos_mm = abs_mm
|
||||
@@ -304,10 +353,15 @@ class ExternalAxis(object):
|
||||
the next line block computes the correct delta.
|
||||
|
||||
The Planner.__encode hook calls this so the AVR comm thread
|
||||
is never blocked by serial RPCs to the ESP."""
|
||||
is never blocked by serial RPCs to the ESP.
|
||||
|
||||
Soft limits are enforced here (defense in depth on top of
|
||||
gplan). Raising stops the planner via Planner.next's
|
||||
exception handler."""
|
||||
if not self.enabled:
|
||||
raise ExternalAxisError(
|
||||
'External axis %r not available' % self.axis_letter)
|
||||
self._check_soft_limit(ext_mm)
|
||||
steps, abs_mm = self._compute_move(ext_mm)
|
||||
# Internal mirror only - drives subsequent delta computation.
|
||||
# state.<axis>p is left to the AVR's status reports.
|
||||
|
||||
Reference in New Issue
Block a user