Skip to content

Commit

Permalink
force_move: Implement CLEAR for SET_KINEMATIC_POSITION (#6262)
Browse files Browse the repository at this point in the history
`CLEAR` clears the homing status (resets the axis limits) without turning off
the motors. This is particularly useful when implementing safe Z homing in
`[homing_override]` on printers with multiple independent Z steppers (where
`FORCE_MOVE` can't be used).

Signed-off-by: Dennis Marttinen <[email protected]>
  • Loading branch information
twelho authored Jan 10, 2025
1 parent 9ca71d8 commit 7083879
Show file tree
Hide file tree
Showing 16 changed files with 77 additions and 46 deletions.
8 changes: 4 additions & 4 deletions docs/Code_Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,10 @@ Useful steps:
be efficient as it is typically only called during homing and
probing operations.
5. Other methods. Implement the `check_move()`, `get_status()`,
`get_steppers()`, `home()`, and `set_position()` methods. These
functions are typically used to provide kinematic specific checks.
However, at the start of development one can use boiler-plate code
here.
`get_steppers()`, `home()`, `clear_homing_state()`, and `set_position()`
methods. These functions are typically used to provide kinematic
specific checks. However, at the start of development one can use
boiler-plate code here.
6. Implement test cases. Create a g-code file with a series of moves
that can test important cases for the given kinematics. Follow the
[debugging documentation](Debugging.md) to convert this g-code file
Expand Down
21 changes: 12 additions & 9 deletions docs/G-Codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,15 +585,18 @@ state; issue a G28 afterwards to reset the kinematics. This command is
intended for low-level diagnostics and debugging.

#### SET_KINEMATIC_POSITION
`SET_KINEMATIC_POSITION [X=<value>] [Y=<value>] [Z=<value>]`: Force
the low-level kinematic code to believe the toolhead is at the given
cartesian position. This is a diagnostic and debugging command; use
SET_GCODE_OFFSET and/or G92 for regular axis transformations. If an
axis is not specified then it will default to the position that the
head was last commanded to. Setting an incorrect or invalid position
may lead to internal software errors. This command may invalidate
future boundary checks; issue a G28 afterwards to reset the
kinematics.
`SET_KINEMATIC_POSITION [X=<value>] [Y=<value>] [Z=<value>]
[CLEAR=<[X][Y][Z]>]`: Force the low-level kinematic code to believe the
toolhead is at the given cartesian position. This is a diagnostic and
debugging command; use SET_GCODE_OFFSET and/or G92 for regular axis
transformations. If an axis is not specified then it will default to the
position that the head was last commanded to. Setting an incorrect or
invalid position may lead to internal software errors. Use the CLEAR
parameter to forget the homing state for the given axes. Note that CLEAR
will not override the previous functionality; if an axis is not specified
to CLEAR it will have its kinematic position set as per above. This
command may invalidate future boundary checks; issue a G28 afterwards to
reset the kinematics.

### [gcode]

Expand Down
7 changes: 6 additions & 1 deletion klippy/extras/force_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,13 @@ def cmd_SET_KINEMATIC_POSITION(self, gcmd):
x = gcmd.get_float('X', curpos[0])
y = gcmd.get_float('Y', curpos[1])
z = gcmd.get_float('Z', curpos[2])
logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f", x, y, z)
clear = gcmd.get('CLEAR', '').upper()
axes = ['X', 'Y', 'Z']
clear_axes = [axes.index(a) for a in axes if a in clear]
logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f clear=%s",
x, y, z, ','.join((axes[i] for i in clear_axes)))
toolhead.set_position([x, y, z, curpos[3]], homing_axes=(0, 1, 2))
toolhead.get_kinematics().clear_homing_state(clear_axes)

def load_config(config):
return ForceMove(config)
3 changes: 1 addition & 2 deletions klippy/extras/safe_z_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ def cmd_G28(self, gcmd):
toolhead.set_position(pos, homing_axes=[2])
toolhead.manual_move([None, None, self.z_hop],
self.z_hop_speed)
if hasattr(toolhead.get_kinematics(), "note_z_not_homed"):
toolhead.get_kinematics().note_z_not_homed()
toolhead.get_kinematics().clear_homing_state((2,))
elif pos[2] < self.z_hop:
# If the Z axis is homed, and below z_hop, lift it to z_hop
toolhead.manual_move([None, None, self.z_hop],
Expand Down
9 changes: 5 additions & 4 deletions klippy/kinematics/cartesian.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ def set_position(self, newpos, homing_axes):
else:
rail = self.rails[axis]
self.limits[axis] = rail.get_range()
def note_z_not_homed(self):
# Helper for Safe Z Home
self.limits[2] = (1.0, -1.0)
def clear_homing_state(self, axes):
for i, _ in enumerate(self.limits):
if i in axes:
self.limits[i] = (1.0, -1.0)
def home_axis(self, homing_state, axis, rail):
# Determine movement
position_min, position_max = rail.get_range()
Expand All @@ -97,7 +98,7 @@ def home(self, homing_state):
else:
self.home_axis(homing_state, axis, self.rails[axis])
def _motor_off(self, print_time):
self.limits = [(1.0, -1.0)] * 3
self.clear_homing_state((0, 1, 2))
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
Expand Down
9 changes: 5 additions & 4 deletions klippy/kinematics/corexy.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ def set_position(self, newpos, homing_axes):
rail.set_position(newpos)
if i in homing_axes:
self.limits[i] = rail.get_range()
def note_z_not_homed(self):
# Helper for Safe Z Home
self.limits[2] = (1.0, -1.0)
def clear_homing_state(self, axes):
for i, _ in enumerate(self.limits):
if i in axes:
self.limits[i] = (1.0, -1.0)
def home(self, homing_state):
# Each axis is homed independently and in order
for axis in homing_state.get_axes():
Expand All @@ -63,7 +64,7 @@ def home(self, homing_state):
# Perform homing
homing_state.home_rails([rail], forcepos, homepos)
def _motor_off(self, print_time):
self.limits = [(1.0, -1.0)] * 3
self.clear_homing_state((0, 1, 2))
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
Expand Down
9 changes: 5 additions & 4 deletions klippy/kinematics/corexz.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ def set_position(self, newpos, homing_axes):
rail.set_position(newpos)
if i in homing_axes:
self.limits[i] = rail.get_range()
def note_z_not_homed(self):
# Helper for Safe Z Home
self.limits[2] = (1.0, -1.0)
def clear_homing_state(self, axes):
for i, _ in enumerate(self.limits):
if i in axes:
self.limits[i] = (1.0, -1.0)
def home(self, homing_state):
# Each axis is homed independently and in order
for axis in homing_state.get_axes():
Expand All @@ -63,7 +64,7 @@ def home(self, homing_state):
# Perform homing
homing_state.home_rails([rail], forcepos, homepos)
def _motor_off(self, print_time):
self.limits = [(1.0, -1.0)] * 3
self.clear_homing_state((0, 1, 2))
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
Expand Down
8 changes: 6 additions & 2 deletions klippy/kinematics/delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,19 @@ def set_position(self, newpos, homing_axes):
self.limit_xy2 = -1.
if tuple(homing_axes) == (0, 1, 2):
self.need_home = False
def clear_homing_state(self, axes):
# Clearing homing state for each axis individually is not implemented
if 0 in axes or 1 in axes or 2 in axes:
self.limit_xy2 = -1
self.need_home = True
def home(self, homing_state):
# All axes are homed simultaneously
homing_state.set_axes([0, 1, 2])
forcepos = list(self.home_position)
forcepos[2] = -1.5 * math.sqrt(max(self.arm2)-self.max_xy2)
homing_state.home_rails(self.rails, forcepos, self.home_position)
def _motor_off(self, print_time):
self.limit_xy2 = -1.
self.need_home = True
self.clear_homing_state((0, 1, 2))
def check_move(self, move):
end_pos = move.end_pos
end_xy2 = end_pos[0]**2 + end_pos[1]**2
Expand Down
4 changes: 4 additions & 0 deletions klippy/kinematics/deltesian.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ def set_position(self, newpos, homing_axes):
rail.set_position(newpos)
for n in homing_axes:
self.homed_axis[n] = True
def clear_homing_state(self, axes):
for i, _ in enumerate(self.limits):
if i in axes:
self.homed_axis[i] = False
def home(self, homing_state):
homing_axes = homing_state.get_axes()
home_xz = 0 in homing_axes or 2 in homing_axes
Expand Down
9 changes: 5 additions & 4 deletions klippy/kinematics/hybrid_corexy.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ def set_position(self, newpos, homing_axes):
else:
rail = self.rails[axis]
self.limits[axis] = rail.get_range()
def note_z_not_homed(self):
# Helper for Safe Z Home
self.limits[2] = (1.0, -1.0)
def clear_homing_state(self, axes):
for i, _ in enumerate(self.limits):
if i in axes:
self.limits[i] = (1.0, -1.0)
def home_axis(self, homing_state, axis, rail):
position_min, position_max = rail.get_range()
hi = rail.get_homing_info()
Expand All @@ -97,7 +98,7 @@ def home(self, homing_state):
else:
self.home_axis(homing_state, axis, self.rails[axis])
def _motor_off(self, print_time):
self.limits = [(1.0, -1.0)] * 3
self.clear_homing_state((0, 1, 2))
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
Expand Down
9 changes: 5 additions & 4 deletions klippy/kinematics/hybrid_corexz.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ def set_position(self, newpos, homing_axes):
else:
rail = self.rails[axis]
self.limits[axis] = rail.get_range()
def note_z_not_homed(self):
# Helper for Safe Z Home
self.limits[2] = (1.0, -1.0)
def clear_homing_state(self, axes):
for i, _ in enumerate(self.limits):
if i in axes:
self.limits[i] = (1.0, -1.0)
def home_axis(self, homing_state, axis, rail):
position_min, position_max = rail.get_range()
hi = rail.get_homing_info()
Expand All @@ -97,7 +98,7 @@ def home(self, homing_state):
else:
self.home_axis(homing_state, axis, self.rails[axis])
def _motor_off(self, print_time):
self.limits = [(1.0, -1.0)] * 3
self.clear_homing_state((0, 1, 2))
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
Expand Down
2 changes: 2 additions & 0 deletions klippy/kinematics/none.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def calc_position(self, stepper_positions):
return [0, 0, 0]
def set_position(self, newpos, homing_axes):
pass
def clear_homing_state(self, axes):
pass
def home(self, homing_state):
pass
def check_move(self, move):
Expand Down
12 changes: 7 additions & 5 deletions klippy/kinematics/polar.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ def set_position(self, newpos, homing_axes):
self.limit_z = self.rails[1].get_range()
if 0 in homing_axes and 1 in homing_axes:
self.limit_xy2 = self.rails[0].get_range()[1]**2
def note_z_not_homed(self):
# Helper for Safe Z Home
self.limit_z = (1.0, -1.0)
def clear_homing_state(self, axes):
if 0 in axes or 1 in axes:
# X and Y cannot be cleared separately
self.limit_xy2 = -1.
if 2 in axes:
self.limit_z = (1.0, -1.0)
def _home_axis(self, homing_state, axis, rail):
# Determine movement
position_min, position_max = rail.get_range()
Expand Down Expand Up @@ -86,8 +89,7 @@ def home(self, homing_state):
if home_z:
self._home_axis(homing_state, 2, self.rails[1])
def _motor_off(self, print_time):
self.limit_z = (1.0, -1.0)
self.limit_xy2 = -1.
self.clear_homing_state((0, 1, 2))
def check_move(self, move):
end_pos = move.end_pos
xy2 = end_pos[0]**2 + end_pos[1]**2
Expand Down
8 changes: 6 additions & 2 deletions klippy/kinematics/rotary_delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ def set_position(self, newpos, homing_axes):
self.limit_xy2 = -1.
if tuple(homing_axes) == (0, 1, 2):
self.need_home = False
def clear_homing_state(self, axes):
# Clearing homing state for each axis individually is not implemented
if 0 in axes or 1 in axes or 2 in axes:
self.limit_xy2 = -1
self.need_home = True
def home(self, homing_state):
# All axes are homed simultaneously
homing_state.set_axes([0, 1, 2])
Expand All @@ -97,8 +102,7 @@ def home(self, homing_state):
forcepos[2] = -1.
homing_state.home_rails(self.rails, forcepos, self.home_position)
def _motor_off(self, print_time):
self.limit_xy2 = -1.
self.need_home = True
self.clear_homing_state((0, 1, 2))
def check_move(self, move):
end_pos = move.end_pos
end_xy2 = end_pos[0]**2 + end_pos[1]**2
Expand Down
3 changes: 3 additions & 0 deletions klippy/kinematics/winch.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def calc_position(self, stepper_positions):
def set_position(self, newpos, homing_axes):
for s in self.steppers:
s.set_position(newpos)
def clear_homing_state(self, axes):
# XXX - homing not implemented
pass
def home(self, homing_state):
# XXX - homing not implemented
homing_state.set_axes([0, 1, 2])
Expand Down
2 changes: 1 addition & 1 deletion klippy/stepper.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ def add_extra_stepper(self, config):
changed_invert = pin_params['invert'] != endstop['invert']
changed_pullup = pin_params['pullup'] != endstop['pullup']
if changed_invert or changed_pullup:
raise error("Pinter rail %s shared endstop pin %s "
raise error("Printer rail %s shared endstop pin %s "
"must specify the same pullup/invert settings" % (
self.get_name(), pin_name))
mcu_endstop.add_stepper(stepper)
Expand Down

0 comments on commit 7083879

Please sign in to comment.