motion_queuing: Add new module to help with motion queues and flushing

Create a new module to assist with host management of motion queues.
Register all MCU_stepper objects with this module and use the module
for step generation.

All steppers will now automatically generate steps whenever
toolhead._advance_flush_time() is invoked.  It is no longer necessary
for callers to individually call stepper.generate_steps().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2025-08-04 21:57:45 -04:00
parent 126275d1f4
commit 9399e738bc
17 changed files with 29 additions and 37 deletions

View File

@ -85,14 +85,13 @@ class ForceMove:
self.trapq_append(self.trapq, print_time, accel_t, cruise_t, accel_t, self.trapq_append(self.trapq, print_time, accel_t, cruise_t, accel_t,
0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel) 0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel)
print_time = print_time + accel_t + cruise_t + accel_t print_time = print_time + accel_t + cruise_t + accel_t
stepper.generate_steps(print_time)
self.trapq_finalize_moves(self.trapq, print_time + 99999.9,
print_time + 99999.9)
stepper.set_trapq(prev_trapq)
stepper.set_stepper_kinematics(prev_sk)
toolhead.note_mcu_movequeue_activity(print_time) toolhead.note_mcu_movequeue_activity(print_time)
toolhead.dwell(accel_t + cruise_t + accel_t) toolhead.dwell(accel_t + cruise_t + accel_t)
toolhead.flush_step_generation() toolhead.flush_step_generation()
stepper.set_trapq(prev_trapq)
stepper.set_stepper_kinematics(prev_sk)
self.trapq_finalize_moves(self.trapq, print_time + 99999.9,
print_time + 99999.9)
def _lookup_stepper(self, gcmd): def _lookup_stepper(self, gcmd):
name = gcmd.get('STEPPER') name = gcmd.get('STEPPER')
if name not in self.steppers: if name not in self.steppers:

View File

@ -76,7 +76,6 @@ class ManualStepper:
self.sync_print_time() self.sync_print_time()
self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos, self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos,
speed, accel) speed, accel)
self.rail.generate_steps(self.next_cmd_time)
self.trapq_finalize_moves(self.trapq, self.next_cmd_time + 99999.9, self.trapq_finalize_moves(self.trapq, self.next_cmd_time + 99999.9,
self.next_cmd_time + 99999.9) self.next_cmd_time + 99999.9)
toolhead = self.printer.lookup_object('toolhead') toolhead = self.printer.lookup_object('toolhead')
@ -138,7 +137,6 @@ class ManualStepper:
raise gcmd.error("Must unregister axis first") raise gcmd.error("Must unregister axis first")
# Unregister # Unregister
toolhead.remove_extra_axis(self) toolhead.remove_extra_axis(self)
toolhead.unregister_step_generator(self.rail.generate_steps)
self.axis_gcode_id = None self.axis_gcode_id = None
return return
if (len(gcode_axis) != 1 or not gcode_axis.isupper() if (len(gcode_axis) != 1 or not gcode_axis.isupper()
@ -155,7 +153,6 @@ class ManualStepper:
self.gaxis_limit_velocity = limit_velocity self.gaxis_limit_velocity = limit_velocity
self.gaxis_limit_accel = limit_accel self.gaxis_limit_accel = limit_accel
toolhead.add_extra_axis(self, self.get_position()[0]) toolhead.add_extra_axis(self, self.get_position()[0])
toolhead.register_step_generator(self.rail.generate_steps)
def process_move(self, print_time, move, ea_index): def process_move(self, print_time, move, ea_index):
axis_r = move.axes_r[ea_index] axis_r = move.axes_r[ea_index]
start_pos = move.start_pos[ea_index] start_pos = move.start_pos[ea_index]
@ -208,7 +205,7 @@ class ManualStepper:
speed, self.homing_accel) speed, self.homing_accel)
# Drip updates to motors # Drip updates to motors
toolhead = self.printer.lookup_object('toolhead') toolhead = self.printer.lookup_object('toolhead')
toolhead.drip_update_time(maxtime, drip_completion, self.steppers) toolhead.drip_update_time(maxtime, drip_completion)
# Clear trapq of any remaining parts of movement # Clear trapq of any remaining parts of movement
reactor = self.printer.get_reactor() reactor = self.printer.get_reactor()
self.trapq_finalize_moves(self.trapq, reactor.NEVER, 0) self.trapq_finalize_moves(self.trapq, reactor.NEVER, 0)

View File

@ -0,0 +1,19 @@
# Helper code for low-level motion queuing and flushing
#
# Copyright (C) 2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
class PrinterMotionQueuing:
def __init__(self, config):
self.printer = config.get_printer()
self.steppers = []
def register_stepper(self, config, stepper):
self.steppers.append(stepper)
def flush_motion_queues(self, must_flush_time, max_step_gen_time):
for stepper in self.steppers:
stepper.generate_steps(max_step_gen_time)
def load_config(config):
return PrinterMotionQueuing(config)

View File

@ -36,7 +36,6 @@ class CartKinematics:
'safe_distance', None, minval=0.)) 'safe_distance', None, minval=0.))
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,

View File

@ -20,7 +20,6 @@ class CoreXYKinematics:
self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z') self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z')
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -20,7 +20,6 @@ class CoreXZKinematics:
self.rails[2].setup_itersolve('corexz_stepper_alloc', b'-') self.rails[2].setup_itersolve('corexz_stepper_alloc', b'-')
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -52,7 +52,6 @@ class DeltaKinematics:
r.setup_itersolve('delta_stepper_alloc', a, t[0], t[1]) r.setup_itersolve('delta_stepper_alloc', a, t[0], t[1])
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
self.need_home = True self.need_home = True
self.limit_xy2 = -1. self.limit_xy2 = -1.

View File

@ -40,7 +40,6 @@ class DeltesianKinematics:
self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'y') self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'y')
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
self.limits = [(1.0, -1.0)] * 3 self.limits = [(1.0, -1.0)] * 3
# X axis limits # X axis limits
min_angle = config.getfloat('min_angle', MIN_ANGLE, min_angle = config.getfloat('min_angle', MIN_ANGLE,

View File

@ -39,8 +39,6 @@ class ExtruderStepper:
self.name, self.cmd_SYNC_EXTRUDER_MOTION, self.name, self.cmd_SYNC_EXTRUDER_MOTION,
desc=self.cmd_SYNC_EXTRUDER_MOTION_help) desc=self.cmd_SYNC_EXTRUDER_MOTION_help)
def _handle_connect(self): def _handle_connect(self):
toolhead = self.printer.lookup_object('toolhead')
toolhead.register_step_generator(self.stepper.generate_steps)
self._set_pressure_advance(self.config_pa, self.config_smooth_time) self._set_pressure_advance(self.config_pa, self.config_smooth_time)
def get_status(self, eventtime): def get_status(self, eventtime):
return {'pressure_advance': self.pressure_advance, return {'pressure_advance': self.pressure_advance,

View File

@ -108,7 +108,6 @@ class GenericCartesianKinematics:
self._load_kinematics(config) self._load_kinematics(config)
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
self.dc_module = None self.dc_module = None
if self.dc_carriages: if self.dc_carriages:
pcs = [dc.get_dual_carriage() for dc in self.dc_carriages] pcs = [dc.get_dual_carriage() for dc in self.dc_carriages]

View File

@ -39,7 +39,6 @@ class HybridCoreXYKinematics:
'safe_distance', None, minval=0.)) 'safe_distance', None, minval=0.))
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -39,7 +39,6 @@ class HybridCoreXZKinematics:
'safe_distance', None, minval=0.)) 'safe_distance', None, minval=0.))
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -21,7 +21,6 @@ class PolarKinematics:
for s in r.get_steppers() ] for s in r.get_steppers() ]
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -52,7 +52,6 @@ class RotaryDeltaKinematics:
math.radians(a), ua, la) math.radians(a), ua, la)
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
self.need_home = True self.need_home = True
self.limit_xy2 = -1. self.limit_xy2 = -1.

View File

@ -21,7 +21,6 @@ class WinchKinematics:
self.anchors.append(a) self.anchors.append(a)
s.setup_itersolve('winch_stepper_alloc', *a) s.setup_itersolve('winch_stepper_alloc', *a)
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
acoords = list(zip(*self.anchors)) acoords = list(zip(*self.anchors))
self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.) self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.)

View File

@ -273,7 +273,8 @@ def PrinterStepper(config, units_in_radians=False):
rotation_dist, steps_per_rotation, rotation_dist, steps_per_rotation,
step_pulse_duration, units_in_radians) step_pulse_duration, units_in_radians)
# Register with helper modules # Register with helper modules
for mname in ['stepper_enable', 'force_move', 'motion_report']: mods = ['stepper_enable', 'force_move', 'motion_report', 'motion_queuing']
for mname in mods:
m = printer.load_object(config, mname) m = printer.load_object(config, mname)
m.register_stepper(config, mcu_stepper) m.register_stepper(config, mcu_stepper)
return mcu_stepper return mcu_stepper
@ -442,9 +443,6 @@ class GenericPrinterRail:
def setup_itersolve(self, alloc_func, *params): def setup_itersolve(self, alloc_func, *params):
for stepper in self.steppers: for stepper in self.steppers:
stepper.setup_itersolve(alloc_func, *params) stepper.setup_itersolve(alloc_func, *params)
def generate_steps(self, flush_time):
for stepper in self.steppers:
stepper.generate_steps(flush_time)
def set_trapq(self, trapq): def set_trapq(self, trapq):
for stepper in self.steppers: for stepper in self.steppers:
stepper.set_trapq(trapq) stepper.set_trapq(trapq)

View File

@ -251,7 +251,7 @@ class ToolHead:
self.trapq_append = ffi_lib.trapq_append self.trapq_append = ffi_lib.trapq_append
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
# Motion flushing # Motion flushing
self.step_generators = [] self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
self.flush_trapqs = [self.trapq] self.flush_trapqs = [self.trapq]
# Create kinematics class # Create kinematics class
gcode = self.printer.lookup_object('gcode') gcode = self.printer.lookup_object('gcode')
@ -280,8 +280,7 @@ class ToolHead:
sg_flush_want = min(flush_time + STEPCOMPRESS_FLUSH_TIME, sg_flush_want = min(flush_time + STEPCOMPRESS_FLUSH_TIME,
self.print_time - self.kin_flush_delay) self.print_time - self.kin_flush_delay)
sg_flush_time = max(sg_flush_want, flush_time) sg_flush_time = max(sg_flush_want, flush_time)
for sg in self.step_generators: self.motion_queuing.flush_motion_queues(flush_time, sg_flush_time)
sg(sg_flush_time)
self.min_restart_time = max(self.min_restart_time, sg_flush_time) self.min_restart_time = max(self.min_restart_time, sg_flush_time)
# Free trapq entries that are no longer needed # Free trapq entries that are no longer needed
clear_history_time = self.clear_history_time clear_history_time = self.clear_history_time
@ -517,7 +516,7 @@ class ToolHead:
def get_extra_axes(self): def get_extra_axes(self):
return [None, None, None] + self.extra_axes return [None, None, None] + self.extra_axes
# Homing "drip move" handling # Homing "drip move" handling
def drip_update_time(self, next_print_time, drip_completion, addstepper=()): def drip_update_time(self, next_print_time, drip_completion):
# Transition from "NeedPrime"/"Priming"/main state to "Drip" state # Transition from "NeedPrime"/"Priming"/main state to "Drip" state
self.special_queuing_state = "Drip" self.special_queuing_state = "Drip"
self.need_check_pause = self.reactor.NEVER self.need_check_pause = self.reactor.NEVER
@ -539,8 +538,6 @@ class ToolHead:
continue continue
npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time) npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time)
self.note_mcu_movequeue_activity(npt + self.kin_flush_delay) self.note_mcu_movequeue_activity(npt + self.kin_flush_delay)
for stepper in addstepper:
stepper.generate_steps(npt)
self._advance_move_time(npt) self._advance_move_time(npt)
# Exit "Drip" state # Exit "Drip" state
self.reactor.update_timer(self.flush_timer, self.reactor.NOW) self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
@ -617,11 +614,6 @@ class ToolHead:
return self.kin return self.kin
def get_trapq(self): def get_trapq(self):
return self.trapq return self.trapq
def register_step_generator(self, handler):
self.step_generators.append(handler)
def unregister_step_generator(self, handler):
if handler in self.step_generators:
self.step_generators.remove(handler)
def note_step_generation_scan_time(self, delay, old_delay=0.): def note_step_generation_scan_time(self, delay, old_delay=0.):
self.flush_step_generation() self.flush_step_generation()
if old_delay: if old_delay: