Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
927e288
toolhead: Avoid LookAheadQueue calling back into toolhead class
KevinOConnor Apr 12, 2025
c71bbb3
toolhead: Avoid toolhead.move() and toolhead._process_moves() in drip…
KevinOConnor Apr 12, 2025
f6f97c4
manual_stepper: Implement "drip moves" for manual stepper STOP_ON_END…
KevinOConnor Apr 12, 2025
c56a3ce
extruder: Rename extruder.move() to extruder.process_move()
KevinOConnor Mar 14, 2025
3a122f9
extruder: Remove update_move_time() call
KevinOConnor Mar 14, 2025
4313868
toolhead: Support unregister_step_generator() call
KevinOConnor Mar 25, 2025
1bb27b8
toolhead: Only alter XYZ coordinates on set_position() calls
KevinOConnor Mar 22, 2025
323d486
force_move: No need to pass 4 parameters to toolhead.set_position()
KevinOConnor Apr 7, 2025
bfa48b0
toolhead: Initial support for adding extra axes to toolhead moves
KevinOConnor Mar 22, 2025
3203f37
bed_mesh: Support toolhead positions with more than 4 axes
KevinOConnor Apr 7, 2025
e3a7015
bed_tilt: Support toolhead positions with more than 4 axes
KevinOConnor Apr 7, 2025
a674c9e
resonance_tester: Support toolhead positions with more than 4 axes
KevinOConnor Apr 7, 2025
3c30ee0
exclude_object: Support toolhead positions with more than 4 axes
KevinOConnor Apr 8, 2025
cfa66a9
skew_correction: Support toolhead positions with more than 4 axes
KevinOConnor Apr 8, 2025
9849797
z_thermal_adjust: Support toolhead positions with more than 4 axes
KevinOConnor Apr 8, 2025
3a07935
gcode_move: Internally track an axis_map to map gcode axis names
KevinOConnor Mar 22, 2025
2387206
gcode_move: Support additional toolhead axes
KevinOConnor Mar 22, 2025
95b090b
manual_stepper: Support registering as an additional axis
KevinOConnor Mar 22, 2025
c804f5b
manual_stepper: Support INSTANTANEOUS_CORNER_VELOCITY on gcode axes
KevinOConnor Apr 30, 2025
091f40d
manual_stepper: Support position_min and position_max options
KevinOConnor Apr 30, 2025
1285e54
manual_stepper: Support LIMIT_VELOCITY and LIMIT_ACCEL when using gco…
KevinOConnor Apr 30, 2025
5ca9fc1
resonance_tester: Fix typo
KevinOConnor May 13, 2025
f438c8c
trad_rack: update for changes in toolhead
as-com Feb 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/Config_Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ All dates in this document are approximate.

## Changes

20260201: The manual_stepper `STOP_ON_ENDSTOP` feature may now take
less time to complete. Previously, the command would wait the entire
time the move could possibly take even if the endstop triggered
earlier. Now, the command finishes shortly after the endstop trigger.

20260121: Kalico now uses automatic monthly release tags in the format
`vYYYY.MM.NN` (e.g., `v2026.01.00`). Users can configure Moonraker to track
stable monthly releases instead of the latest commits. See
Expand Down
7 changes: 7 additions & 0 deletions docs/Config_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -3104,6 +3104,13 @@ printer kinematics.
# Endstop switch detection pin. If specified, then one may perform
# "homing moves" by adding a STOP_ON_ENDSTOP parameter to
# MANUAL_STEPPER movement commands.
#position_min:
#position_max:
# The minimum and maximum position the stepper can be commanded to
# move to. If specified then one may not command the stepper to move
# past the given position. Note that these limits do not prevent
# setting an arbitrary position with the `MANUAL_STEPPER
# SET_POSITION=x` command. The default is to not enforce a limit.
```

### [mixing_extruder]
Expand Down
19 changes: 19 additions & 0 deletions docs/G-Codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,25 @@ scheduled to run after the stepper move completes, however if a manual
stepper move uses SYNC=0 then future G-Code movement commands may run
in parallel with the stepper movement.

`MANUAL_STEPPER STEPPER=config_name GCODE_AXIS=[A-Z]
[LIMIT_VELOCITY=<velocity>] [LIMIT_ACCEL=<accel>]
[INSTANTANEOUS_CORNER_VELOCITY=<velocity>]`: If the `GCODE_AXIS`
parameter is specified then it configures the stepper motor as an
extra axis on `G1` move commands. For example, if one were to issue a
`MANUAL_STEPPER ... GCODE_AXIS=R` command then one could issue
commands like `G1 X10 Y20 R30` to move the stepper motor. The
resulting moves will occur synchronously with the associated toolhead
xyz movements. If the motor is associated with a `GCODE_AXIS` then
one may no longer issue movements using the above `MANUAL_STEPPER`
command - one may unregister the stepper with a `MANUAL_STEPPER
... GCODE_AXIS=` command to resume manual control of the motor. The
`LIMIT_VELOCITY` and `LIMIT_ACCEL` parameters allow one to reduce the
speed of `G1` moves if those moves would result in a velocity or
acceleration above the specified limits. The
`INSTANTANEOUS_CORNER_VELOCITY` specifies the maximum instantaneous
velocity change (in mm/s) of the motor during the junction of two
moves (the default is 1mm/s).

### [mcp4018]

The following command is available when a
Expand Down
22 changes: 10 additions & 12 deletions klippy/extras/bed_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ def get_position(self):
self.last_position[2] -= self.fade_target
else:
# return current position minus the current z-adjustment
x, y, z, e = self.toolhead.get_position()
cur_pos = self.toolhead.get_position()
x, y, z = cur_pos[:3]
max_adj = self.z_mesh.calc_z(x, y)
factor = 1.0
z_adj = max_adj - self.fade_target
Expand All @@ -260,21 +261,21 @@ def get_position(self):
)
factor = constrain(factor, 0.0, 1.0)
final_z_adj = factor * z_adj + self.fade_target
self.last_position[:] = [x, y, z - final_z_adj, e]
self.last_position[:] = [x, y, z - final_z_adj] + cur_pos[3:]
return list(self.last_position)

def move(self, newpos, speed):
factor = self.get_z_factor(newpos[2])
if self.z_mesh is None or not factor:
# No mesh calibrated, or mesh leveling phased out.
x, y, z, e = newpos
x, y, z = newpos[:3]
if self.log_fade_complete:
self.log_fade_complete = False
logging.info(
"bed_mesh fade complete: Current Z: %.4f fade_target: %.4f "
% (z, self.fade_target)
)
self.toolhead.move([x, y, z + self.fade_target, e], speed)
self.toolhead.move([x, y, z + self.fade_target] + newpos[3:], speed)
else:
self.splitter.build_move(self.last_position, newpos, factor)
while not self.splitter.traverse_complete:
Expand Down Expand Up @@ -1188,7 +1189,7 @@ def build_move(self, prev_pos, next_pos, factor):
self.z_offset = self._calc_z_offset(prev_pos)
self.traverse_complete = False
self.distance_checked = 0.0
axes_d = [self.next_pos[i] - self.prev_pos[i] for i in range(4)]
axes_d = [np - pp for np, pp in zip(self.next_pos, self.prev_pos)]
self.total_move_length = math.sqrt(sum([d * d for d in axes_d[:3]]))
self.axis_move = [not isclose(d, 0.0, abs_tol=1e-10) for d in axes_d]

Expand All @@ -1204,7 +1205,7 @@ def _set_next_move(self, distance_from_prev):
"bed_mesh: Slice distance is negative "
"or greater than entire move length"
)
for i in range(4):
for i in range(len(self.next_pos)):
if self.axis_move[i]:
self.current_pos[i] = lerp(
t, self.prev_pos[i], self.next_pos[i]
Expand All @@ -1223,12 +1224,9 @@ def split(self):
next_z = self._calc_z_offset(self.current_pos)
if abs(next_z - self.z_offset) >= self.split_delta_z:
self.z_offset = next_z
return (
self.current_pos[0],
self.current_pos[1],
self.current_pos[2] + self.z_offset,
self.current_pos[3],
)
newpos = list(self.current_pos)
newpos[2] += self.z_offset
return newpos
# end of move reached
self.current_pos[:] = self.next_pos
self.z_offset = self._calc_z_offset(self.current_pos)
Expand Down
24 changes: 7 additions & 17 deletions klippy/extras/bed_tilt.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,15 @@ def handle_connect(self):
self.toolhead = self.printer.lookup_object("toolhead")

def get_position(self):
x, y, z, e = self.toolhead.get_position()
return [
x,
y,
z - x * self.x_adjust - y * self.y_adjust - self.z_adjust,
e,
]
pos = self.toolhead.get_position()
x, y, z = pos[:3]
z -= x * self.x_adjust + y * self.y_adjust + self.z_adjust
return [x, y, z] + pos[3:]

def move(self, newpos, speed):
x, y, z, e = newpos
self.toolhead.move(
[
x,
y,
z + x * self.x_adjust + y * self.y_adjust + self.z_adjust,
e,
],
speed,
)
x, y, z = newpos[:3]
z += x * self.x_adjust + y * self.y_adjust + self.z_adjust
self.toolhead.move([x, y, z] + newpos[3:], speed)

def update_adjust(self, x_adjust, y_adjust, z_adjust):
self.x_adjust = x_adjust
Expand Down
36 changes: 18 additions & 18 deletions klippy/extras/exclude_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,26 +102,25 @@ def _reset_file(self):
self._reset_state()
self._unregister_transform()

def _get_extrusion_offsets(self):
offset = self.extrusion_offsets.get(
self.toolhead.get_extruder().get_name()
)
def _get_extrusion_offsets(self, num_coord):
ename = self.toolhead.get_extruder().get_name()
offset = self.extrusion_offsets.get(ename)
if offset is None:
offset = [0.0, 0.0, 0.0, 0.0]
self.extrusion_offsets[self.toolhead.get_extruder().get_name()] = (
offset
)
offset = [0.0] * num_coord
self.extrusion_offsets[ename] = offset
if len(offset) < num_coord:
offset.extend([0.0] * (len(num_coord) - len(offset)))
return offset

def get_position(self):
offset = self._get_extrusion_offsets()
pos = self.next_transform.get_position()
for i in range(4):
offset = self._get_extrusion_offsets(len(pos))
for i in range(len(pos)):
self.last_position[i] = pos[i] + offset[i]
return list(self.last_position)

def _normal_move(self, newpos, speed):
offset = self._get_extrusion_offsets()
offset = self._get_extrusion_offsets(len(newpos))

if (
self.initial_extrusion_moves > 0
Expand All @@ -147,9 +146,9 @@ def _normal_move(self, newpos, speed):
newpos[0] != self.last_position_excluded[0]
or newpos[1] != self.last_position_excluded[1]
):
offset[0] = 0
offset[1] = 0
offset[2] = 0
for i in range(len(newpos)):
if i != 3:
offset[i] = 0
offset[3] += self.extruder_adj
self.extruder_adj = 0

Expand All @@ -164,14 +163,15 @@ def _normal_move(self, newpos, speed):
self.extruder_adj = 0

tx_pos = newpos[:]
for i in range(4):
for i in range(len(newpos)):
tx_pos[i] = newpos[i] - offset[i]
self.next_transform.move(tx_pos, speed)

def _ignore_move(self, newpos, speed):
offset = self._get_extrusion_offsets()
for i in range(3):
offset[i] = newpos[i] - self.last_position_extruded[i]
offset = self._get_extrusion_offsets(len(newpos))
for i in range(len(newpos)):
if i != 3:
offset[i] = newpos[i] - self.last_position_extruded[i]
offset[3] = offset[3] + newpos[3] - self.last_position[3]
self.last_position[:] = newpos
self.last_position_excluded[:] = self.last_position
Expand Down
2 changes: 1 addition & 1 deletion klippy/extras/force_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def cmd_SET_KINEMATIC_POSITION(self, gcmd):
z,
",".join((axes[i] for i in clear_axes)),
)
toolhead.set_position([x, y, z, curpos[3]], homing_axes=(0, 1, 2))
toolhead.set_position([x, y, z], homing_axes=(0, 1, 2))
toolhead.get_kinematics().clear_homing_state(clear_axes)


Expand Down
50 changes: 33 additions & 17 deletions klippy/extras/gcode_move.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# G-Code G1 movement commands (and associated coordinate manipulation)
#
# Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
Expand All @@ -17,6 +17,9 @@ def __init__(self, config):
printer.register_event_handler(
"toolhead:manual_move", self.reset_last_position
)
printer.register_event_handler(
"toolhead:update_extra_axes", self._update_extra_axes
)
printer.register_event_handler(
"gcode:command_error", self.reset_last_position
)
Expand Down Expand Up @@ -62,6 +65,7 @@ def __init__(self, config):
self.base_position = [0.0, 0.0, 0.0, 0.0]
self.last_position = [0.0, 0.0, 0.0, 0.0]
self.homing_position = [0.0, 0.0, 0.0, 0.0]
self.axis_map = {"X": 0, "Y": 1, "Z": 2, "E": 3}
self.speed = 25.0
self.speed_factor = 1.0 / 60.0
self.extrude_factor = 1.0
Expand Down Expand Up @@ -138,37 +142,49 @@ def get_status(self, eventtime=None):
"extrude_factor": self.extrude_factor,
"absolute_coordinates": self.absolute_coord,
"absolute_extrude": self.absolute_extrude,
"homing_origin": self.Coord(*self.homing_position),
"position": self.Coord(*self.last_position),
"gcode_position": self.Coord(*move_position),
"homing_origin": self.Coord(*self.homing_position[:4]),
"position": self.Coord(*self.last_position[:4]),
"gcode_position": self.Coord(*move_position[:4]),
}

def reset_last_position(self):
if self.is_printer_ready:
self.last_position = self.position_with_transform()

def _update_extra_axes(self):
toolhead = self.printer.lookup_object("toolhead")
axis_map = {"X": 0, "Y": 1, "Z": 2, "E": 3}
extra_axes = toolhead.get_extra_axes()
for index, ea in enumerate(extra_axes):
if ea is None:
continue
gcode_id = ea.get_axis_gcode_id()
if gcode_id is None or gcode_id in axis_map or gcode_id in "FN":
continue
axis_map[gcode_id] = index
self.axis_map = axis_map
self.base_position[4:] = [0.0] * (len(extra_axes) - 4)
self.reset_last_position()

# G-Code movement commands
def cmd_G1(self, gcmd):
# Move
params = gcmd.get_command_parameters()
try:
for pos, axis in enumerate("XYZ"):
for axis, pos in self.axis_map.items():
if axis in params:
v = float(params[axis])
if not self.absolute_coord:
absolute_coord = self.absolute_coord
if axis == "E":
v *= self.extrude_factor
if not self.absolute_extrude:
absolute_coord = False
if not absolute_coord:
# value relative to position of last move
self.last_position[pos] += v
else:
# value relative to base coordinate position
self.last_position[pos] = v + self.base_position[pos]
if "E" in params:
v = float(params["E"]) * self.extrude_factor
if not self.absolute_coord or not self.absolute_extrude:
# value relative to position of last move
self.last_position[3] += v
else:
# value relative to base coordinate position
self.last_position[3] = v + self.base_position[3]
if "F" in params:
gcode_speed = float(params["F"])
if gcode_speed <= 0.0:
Expand Down Expand Up @@ -216,7 +232,7 @@ def cmd_G92(self, gcmd):
offset *= self.extrude_factor
self.base_position[i] = self.last_position[i] - offset
if offsets == [None, None, None, None]:
self.base_position = list(self.last_position)
self.base_position[:4] = self.last_position[:4]

def cmd_M114(self, gcmd):
# Get Current Position
Expand Down Expand Up @@ -284,7 +300,7 @@ def cmd_RESTORE_GCODE_STATE(self, gcmd):
# Restore state
self.absolute_coord = state["absolute_coord"]
self.absolute_extrude = state["absolute_extrude"]
self.base_position = list(state["base_position"])
self.base_position[:4] = state["base_position"][:4]
self.homing_position = list(state["homing_position"])
self.speed = state["speed"]
self.speed_factor = state["speed_factor"]
Expand Down Expand Up @@ -318,7 +334,7 @@ def cmd_GET_POSITION(self, gcmd):
toolhead_pos = " ".join(
[
"%s:%.6f" % (a, v)
for a, v in zip("XYZE", toolhead.get_position())
for a, v in zip("XYZE", toolhead.get_position()[:4])
]
)
gcode_pos = " ".join(
Expand Down
Loading
Loading