diff options
Diffstat (limited to 'klippy/kinematics')
-rw-r--r-- | klippy/kinematics/cartesian.py | 89 | ||||
-rw-r--r-- | klippy/kinematics/corexy.py | 54 | ||||
-rw-r--r-- | klippy/kinematics/corexz.py | 54 | ||||
-rw-r--r-- | klippy/kinematics/delta.py | 283 | ||||
-rw-r--r-- | klippy/kinematics/deltesian.py | 150 | ||||
-rw-r--r-- | klippy/kinematics/extruder.py | 302 | ||||
-rw-r--r-- | klippy/kinematics/generic_cartesian.py | 227 | ||||
-rw-r--r-- | klippy/kinematics/hybrid_corexy.py | 88 | ||||
-rw-r--r-- | klippy/kinematics/hybrid_corexz.py | 88 | ||||
-rw-r--r-- | klippy/kinematics/idex_modes.py | 248 | ||||
-rw-r--r-- | klippy/kinematics/kinematic_stepper.py | 65 | ||||
-rw-r--r-- | klippy/kinematics/none.py | 17 | ||||
-rw-r--r-- | klippy/kinematics/polar.py | 66 | ||||
-rw-r--r-- | klippy/kinematics/rotary_delta.py | 277 | ||||
-rw-r--r-- | klippy/kinematics/winch.py | 31 |
15 files changed, 1289 insertions, 750 deletions
diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index f9030275..4d21c6e1 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -7,59 +7,70 @@ import logging import stepper from . import idex_modes + class CartKinematics: def __init__(self, toolhead, config): self.printer = config.get_printer() # Setup axis rails self.dual_carriage_axis = None self.dual_carriage_rails = [] - self.rails = [stepper.LookupMultiRail(config.getsection('stepper_' + n)) - for n in 'xyz'] - for rail, axis in zip(self.rails, 'xyz'): - rail.setup_itersolve('cartesian_stepper_alloc', axis.encode()) + self.rails = [ + stepper.LookupMultiRail(config.getsection("stepper_" + n)) for n in "xyz" + ] + for rail, axis in zip(self.rails, "xyz"): + rail.setup_itersolve("cartesian_stepper_alloc", axis.encode()) ranges = [r.get_range() for r in self.rails] - self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.) - self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.) + self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0) + self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0) self.dc_module = None - if config.has_section('dual_carriage'): - dc_config = config.getsection('dual_carriage') - dc_axis = dc_config.getchoice('axis', ['x', 'y']) - self.dual_carriage_axis = {'x': 0, 'y': 1}[dc_axis] + if config.has_section("dual_carriage"): + dc_config = config.getsection("dual_carriage") + dc_axis = dc_config.getchoice("axis", ["x", "y"]) + self.dual_carriage_axis = {"x": 0, "y": 1}[dc_axis] # setup second dual carriage rail self.rails.append(stepper.LookupMultiRail(dc_config)) - self.rails[3].setup_itersolve('cartesian_stepper_alloc', - dc_axis.encode()) + self.rails[3].setup_itersolve("cartesian_stepper_alloc", dc_axis.encode()) self.dc_module = idex_modes.DualCarriages( - self.printer, [self.rails[self.dual_carriage_axis]], - [self.rails[3]], axes=[self.dual_carriage_axis], - safe_dist=dc_config.getfloat( - 'safe_distance', None, minval=0.)) + self.printer, + [self.rails[self.dual_carriage_axis]], + [self.rails[3]], + axes=[self.dual_carriage_axis], + safe_dist=dc_config.getfloat("safe_distance", None, minval=0.0), + ) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() - self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, - above=0., maxval=max_velocity) - self.max_z_accel = config.getfloat('max_z_accel', max_accel, - above=0., maxval=max_accel) + self.max_z_velocity = config.getfloat( + "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity + ) + self.max_z_accel = config.getfloat( + "max_z_accel", max_accel, above=0.0, maxval=max_accel + ) self.limits = [(1.0, -1.0)] * 3 + def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] + def calc_position(self, stepper_positions): rails = self.rails if self.dc_module: - primary_rail = self.dc_module.get_primary_rail( - self.dual_carriage_axis) - rails = (rails[:self.dual_carriage_axis] + - [primary_rail] + rails[self.dual_carriage_axis+1:]) + primary_rail = self.dc_module.get_primary_rail(self.dual_carriage_axis) + rails = ( + rails[: self.dual_carriage_axis] + + [primary_rail] + + rails[self.dual_carriage_axis + 1 :] + ) return [stepper_positions[rail.get_name()] for rail in rails] + def update_limits(self, i, range): l, h = self.limits[i] # Only update limits if this axis was already homed, # otherwise leave in un-homed state. if l <= h: self.limits[i] = range + def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) @@ -70,10 +81,12 @@ class CartKinematics: else: rail = self.rails[axis] self.limits[axis] = rail.get_range() + def clear_homing_state(self, clear_axes): for axis, axis_name in enumerate("xyz"): if axis_name in clear_axes: self.limits[axis] = (1.0, -1.0) + def home_axis(self, homing_state, axis, rail): # Determine movement position_min, position_max = rail.get_range() @@ -87,6 +100,7 @@ class CartKinematics: forcepos[axis] += 1.5 * (position_max - hi.position_endstop) # Perform homing homing_state.home_rails([rail], forcepos, homepos) + def home(self, homing_state): # Each axis is homed independently and in order for axis in homing_state.get_axes(): @@ -94,20 +108,26 @@ class CartKinematics: self.dc_module.home(homing_state, self.dual_carriage_axis) else: self.home_axis(homing_state, axis, self.rails[axis]) + def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): - if (move.axes_d[i] - and (end_pos[i] < self.limits[i][0] - or end_pos[i] > self.limits[i][1])): + if move.axes_d[i] and ( + end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1] + ): if self.limits[i][0] > self.limits[i][1]: raise move.move_error("Must home axis first") raise move.move_error() + def check_move(self, move): limits = self.limits xpos, ypos = move.end_pos[:2] - if (xpos < limits[0][0] or xpos > limits[0][1] - or ypos < limits[1][0] or ypos > limits[1][1]): + if ( + xpos < limits[0][0] + or xpos > limits[0][1] + or ypos < limits[1][0] + or ypos > limits[1][1] + ): self._check_endstops(move) if not move.axes_d[2]: # Normal XY move - use defaults @@ -115,15 +135,16 @@ class CartKinematics: # Move with Z - update velocity and accel for slower Z axis self._check_endstops(move) z_ratio = move.move_d / abs(move.axes_d[2]) - move.limit_speed( - self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + def get_status(self, eventtime): axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h] return { - 'homed_axes': "".join(axes), - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max, + "homed_axes": "".join(axes), + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, } + def load_kinematics(toolhead, config): return CartKinematics(toolhead, config) diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py index d68f8854..a571b2f6 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -6,45 +6,54 @@ import logging, math import stepper + class CoreXYKinematics: def __init__(self, toolhead, config): # Setup axis rails - self.rails = [stepper.LookupMultiRail(config.getsection('stepper_' + n)) - for n in 'xyz'] + self.rails = [ + stepper.LookupMultiRail(config.getsection("stepper_" + n)) for n in "xyz" + ] for s in self.rails[1].get_steppers(): self.rails[0].get_endstops()[0][0].add_stepper(s) for s in self.rails[0].get_steppers(): self.rails[1].get_endstops()[0][0].add_stepper(s) - self.rails[0].setup_itersolve('corexy_stepper_alloc', b'+') - self.rails[1].setup_itersolve('corexy_stepper_alloc', b'-') - self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z') + self.rails[0].setup_itersolve("corexy_stepper_alloc", b"+") + self.rails[1].setup_itersolve("corexy_stepper_alloc", b"-") + self.rails[2].setup_itersolve("cartesian_stepper_alloc", b"z") for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( - 'max_z_velocity', max_velocity, above=0., maxval=max_velocity) + "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity + ) self.max_z_accel = config.getfloat( - 'max_z_accel', max_accel, above=0., maxval=max_accel) + "max_z_accel", max_accel, above=0.0, maxval=max_accel + ) self.limits = [(1.0, -1.0)] * 3 ranges = [r.get_range() for r in self.rails] - self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.) - self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.) + self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0) + self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0) + def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] + def calc_position(self, stepper_positions): pos = [stepper_positions[rail.get_name()] for rail in self.rails] return [0.5 * (pos[0] + pos[1]), 0.5 * (pos[0] - pos[1]), pos[2]] + def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) if "xyz"[i] in homing_axes: self.limits[i] = rail.get_range() + def clear_homing_state(self, clear_axes): for axis, axis_name in enumerate("xyz"): if axis_name in clear_axes: self.limits[axis] = (1.0, -1.0) + def home(self, homing_state): # Each axis is homed independently and in order for axis in homing_state.get_axes(): @@ -61,20 +70,26 @@ class CoreXYKinematics: forcepos[axis] += 1.5 * (position_max - hi.position_endstop) # Perform homing homing_state.home_rails([rail], forcepos, homepos) + def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): - if (move.axes_d[i] - and (end_pos[i] < self.limits[i][0] - or end_pos[i] > self.limits[i][1])): + if move.axes_d[i] and ( + end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1] + ): if self.limits[i][0] > self.limits[i][1]: raise move.move_error("Must home axis first") raise move.move_error() + def check_move(self, move): limits = self.limits xpos, ypos = move.end_pos[:2] - if (xpos < limits[0][0] or xpos > limits[0][1] - or ypos < limits[1][0] or ypos > limits[1][1]): + if ( + xpos < limits[0][0] + or xpos > limits[0][1] + or ypos < limits[1][0] + or ypos > limits[1][1] + ): self._check_endstops(move) if not move.axes_d[2]: # Normal XY move - use defaults @@ -82,15 +97,16 @@ class CoreXYKinematics: # Move with Z - update velocity and accel for slower Z axis self._check_endstops(move) z_ratio = move.move_d / abs(move.axes_d[2]) - move.limit_speed( - self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + def get_status(self, eventtime): axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h] return { - 'homed_axes': "".join(axes), - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max, + "homed_axes": "".join(axes), + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, } + def load_kinematics(toolhead, config): return CoreXYKinematics(toolhead, config) diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py index 8d3e027c..b75e00c0 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -6,45 +6,54 @@ import logging, math import stepper + class CoreXZKinematics: def __init__(self, toolhead, config): # Setup axis rails - self.rails = [stepper.LookupMultiRail(config.getsection('stepper_' + n)) - for n in 'xyz'] + self.rails = [ + stepper.LookupMultiRail(config.getsection("stepper_" + n)) for n in "xyz" + ] for s in self.rails[0].get_steppers(): self.rails[2].get_endstops()[0][0].add_stepper(s) for s in self.rails[2].get_steppers(): self.rails[0].get_endstops()[0][0].add_stepper(s) - self.rails[0].setup_itersolve('corexz_stepper_alloc', b'+') - self.rails[1].setup_itersolve('cartesian_stepper_alloc', b'y') - self.rails[2].setup_itersolve('corexz_stepper_alloc', b'-') + self.rails[0].setup_itersolve("corexz_stepper_alloc", b"+") + self.rails[1].setup_itersolve("cartesian_stepper_alloc", b"y") + self.rails[2].setup_itersolve("corexz_stepper_alloc", b"-") for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( - 'max_z_velocity', max_velocity, above=0., maxval=max_velocity) + "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity + ) self.max_z_accel = config.getfloat( - 'max_z_accel', max_accel, above=0., maxval=max_accel) + "max_z_accel", max_accel, above=0.0, maxval=max_accel + ) self.limits = [(1.0, -1.0)] * 3 ranges = [r.get_range() for r in self.rails] - self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.) - self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.) + self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0) + self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0) + def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] + def calc_position(self, stepper_positions): pos = [stepper_positions[rail.get_name()] for rail in self.rails] return [0.5 * (pos[0] + pos[2]), pos[1], 0.5 * (pos[0] - pos[2])] + def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) if "xyz"[i] in homing_axes: self.limits[i] = rail.get_range() + def clear_homing_state(self, clear_axes): for axis, axis_name in enumerate("xyz"): if axis_name in clear_axes: self.limits[axis] = (1.0, -1.0) + def home(self, homing_state): # Each axis is homed independently and in order for axis in homing_state.get_axes(): @@ -61,20 +70,26 @@ class CoreXZKinematics: forcepos[axis] += 1.5 * (position_max - hi.position_endstop) # Perform homing homing_state.home_rails([rail], forcepos, homepos) + def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): - if (move.axes_d[i] - and (end_pos[i] < self.limits[i][0] - or end_pos[i] > self.limits[i][1])): + if move.axes_d[i] and ( + end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1] + ): if self.limits[i][0] > self.limits[i][1]: raise move.move_error("Must home axis first") raise move.move_error() + def check_move(self, move): limits = self.limits xpos, ypos = move.end_pos[:2] - if (xpos < limits[0][0] or xpos > limits[0][1] - or ypos < limits[1][0] or ypos > limits[1][1]): + if ( + xpos < limits[0][0] + or xpos > limits[0][1] + or ypos < limits[1][0] + or ypos > limits[1][1] + ): self._check_endstops(move) if not move.axes_d[2]: # Normal XY move - use defaults @@ -82,15 +97,16 @@ class CoreXZKinematics: # Move with Z - update velocity and accel for slower Z axis self._check_endstops(move) z_ratio = move.move_d / abs(move.axes_d[2]) - move.limit_speed( - self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + def get_status(self, eventtime): axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h] return { - 'homed_axes': "".join(axes), - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max, + "homed_axes": "".join(axes), + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, } + def load_kinematics(toolhead, config): return CoreXZKinematics(toolhead, config) diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index aa244a8f..508a3331 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -7,116 +7,148 @@ import math, logging import stepper, mathutil # Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO -SLOW_RATIO = 3. +SLOW_RATIO = 3.0 + class DeltaKinematics: def __init__(self, toolhead, config): # Setup tower rails - stepper_configs = [config.getsection('stepper_' + a) for a in 'abc'] - rail_a = stepper.LookupMultiRail( - stepper_configs[0], need_position_minmax = False) + stepper_configs = [config.getsection("stepper_" + a) for a in "abc"] + rail_a = stepper.LookupMultiRail(stepper_configs[0], need_position_minmax=False) a_endstop = rail_a.get_homing_info().position_endstop rail_b = stepper.LookupMultiRail( - stepper_configs[1], need_position_minmax = False, - default_position_endstop=a_endstop) + stepper_configs[1], + need_position_minmax=False, + default_position_endstop=a_endstop, + ) rail_c = stepper.LookupMultiRail( - stepper_configs[2], need_position_minmax = False, - default_position_endstop=a_endstop) + stepper_configs[2], + need_position_minmax=False, + default_position_endstop=a_endstop, + ) self.rails = [rail_a, rail_b, rail_c] # Setup max velocity self.max_velocity, self.max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( - 'max_z_velocity', self.max_velocity, - above=0., maxval=self.max_velocity) - self.max_z_accel = config.getfloat('max_z_accel', self.max_accel, - above=0., maxval=self.max_accel) + "max_z_velocity", self.max_velocity, above=0.0, maxval=self.max_velocity + ) + self.max_z_accel = config.getfloat( + "max_z_accel", self.max_accel, above=0.0, maxval=self.max_accel + ) # Read radius and arm lengths - self.radius = radius = config.getfloat('delta_radius', above=0.) - print_radius = config.getfloat('print_radius', radius, above=0.) - arm_length_a = stepper_configs[0].getfloat('arm_length', above=radius) + self.radius = radius = config.getfloat("delta_radius", above=0.0) + print_radius = config.getfloat("print_radius", radius, above=0.0) + arm_length_a = stepper_configs[0].getfloat("arm_length", above=radius) self.arm_lengths = arm_lengths = [ - sconfig.getfloat('arm_length', arm_length_a, above=radius) - for sconfig in stepper_configs] + sconfig.getfloat("arm_length", arm_length_a, above=radius) + for sconfig in stepper_configs + ] self.arm2 = [arm**2 for arm in arm_lengths] - self.abs_endstops = [(rail.get_homing_info().position_endstop - + math.sqrt(arm2 - radius**2)) - for rail, arm2 in zip(self.rails, self.arm2)] + self.abs_endstops = [ + (rail.get_homing_info().position_endstop + math.sqrt(arm2 - radius**2)) + for rail, arm2 in zip(self.rails, self.arm2) + ] # Determine tower locations in cartesian space - self.angles = [sconfig.getfloat('angle', angle) - for sconfig, angle in zip(stepper_configs, - [210., 330., 90.])] - self.towers = [(math.cos(math.radians(angle)) * radius, - math.sin(math.radians(angle)) * radius) - for angle in self.angles] + self.angles = [ + sconfig.getfloat("angle", angle) + for sconfig, angle in zip(stepper_configs, [210.0, 330.0, 90.0]) + ] + self.towers = [ + ( + math.cos(math.radians(angle)) * radius, + math.sin(math.radians(angle)) * radius, + ) + for angle in self.angles + ] for r, a, t in zip(self.rails, self.arm2, self.towers): - 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(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) # Setup boundary checks self.need_home = True - self.limit_xy2 = -1. - self.home_position = tuple( - self._actuator_to_cartesian(self.abs_endstops)) - self.max_z = min([rail.get_homing_info().position_endstop - for rail in self.rails]) - self.min_z = config.getfloat('minimum_z_position', 0, maxval=self.max_z) - self.limit_z = min([ep - arm - for ep, arm in zip(self.abs_endstops, arm_lengths)]) + self.limit_xy2 = -1.0 + self.home_position = tuple(self._actuator_to_cartesian(self.abs_endstops)) + self.max_z = min( + [rail.get_homing_info().position_endstop for rail in self.rails] + ) + self.min_z = config.getfloat("minimum_z_position", 0, maxval=self.max_z) + self.limit_z = min( + [ep - arm for ep, arm in zip(self.abs_endstops, arm_lengths)] + ) self.min_arm_length = min_arm_length = min(arm_lengths) self.min_arm2 = min_arm_length**2 logging.info( "Delta max build height %.2fmm (radius tapered above %.2fmm)" - % (self.max_z, self.limit_z)) + % (self.max_z, self.limit_z) + ) # Find the point where an XY move could result in excessive # tower movement - half_min_step_dist = min([r.get_steppers()[0].get_step_dist() - for r in self.rails]) * .5 + half_min_step_dist = ( + min([r.get_steppers()[0].get_step_dist() for r in self.rails]) * 0.5 + ) min_arm_length = min(arm_lengths) + def ratio_to_xy(ratio): - return (ratio * math.sqrt(min_arm_length**2 / (ratio**2 + 1.) - - half_min_step_dist**2) - + half_min_step_dist - radius) - self.slow_xy2 = ratio_to_xy(SLOW_RATIO)**2 - self.very_slow_xy2 = ratio_to_xy(2. * SLOW_RATIO)**2 - self.max_xy2 = min(print_radius, min_arm_length - radius, - ratio_to_xy(4. * SLOW_RATIO))**2 + return ( + ratio + * math.sqrt( + min_arm_length**2 / (ratio**2 + 1.0) - half_min_step_dist**2 + ) + + half_min_step_dist + - radius + ) + + self.slow_xy2 = ratio_to_xy(SLOW_RATIO) ** 2 + self.very_slow_xy2 = ratio_to_xy(2.0 * SLOW_RATIO) ** 2 + self.max_xy2 = ( + min(print_radius, min_arm_length - radius, ratio_to_xy(4.0 * SLOW_RATIO)) + ** 2 + ) max_xy = math.sqrt(self.max_xy2) - logging.info("Delta max build radius %.2fmm (moves slowed past %.2fmm" - " and %.2fmm)" - % (max_xy, math.sqrt(self.slow_xy2), - math.sqrt(self.very_slow_xy2))) - self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.) - self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.) - self.set_position([0., 0., 0.], "") + logging.info( + "Delta max build radius %.2fmm (moves slowed past %.2fmm" + " and %.2fmm)" + % (max_xy, math.sqrt(self.slow_xy2), math.sqrt(self.very_slow_xy2)) + ) + self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.0) + self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.0) + self.set_position([0.0, 0.0, 0.0], "") + def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] + def _actuator_to_cartesian(self, spos): sphere_coords = [(t[0], t[1], sp) for t, sp in zip(self.towers, spos)] return mathutil.trilateration(sphere_coords, self.arm2) + def calc_position(self, stepper_positions): spos = [stepper_positions[rail.get_name()] for rail in self.rails] return self._actuator_to_cartesian(spos) + def set_position(self, newpos, homing_axes): for rail in self.rails: rail.set_position(newpos) - self.limit_xy2 = -1. + self.limit_xy2 = -1.0 if homing_axes == "xyz": self.need_home = False + def clear_homing_state(self, clear_axes): # Clearing homing state for each axis individually is not implemented if clear_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) + forcepos[2] = -1.5 * math.sqrt(max(self.arm2) - self.max_xy2) homing_state.home_rails(self.rails, forcepos, self.home_position) + def check_move(self, move): end_pos = move.end_pos - end_xy2 = end_pos[0]**2 + end_pos[1]**2 + end_xy2 = end_pos[0] ** 2 + end_pos[1] ** 2 if end_xy2 <= self.limit_xy2 and not move.axes_d[2]: # Normal XY move return @@ -127,44 +159,48 @@ class DeltaKinematics: if end_z > self.limit_z: above_z_limit = end_z - self.limit_z allowed_radius = self.radius - math.sqrt( - self.min_arm2 - (self.min_arm_length - above_z_limit)**2 + self.min_arm2 - (self.min_arm_length - above_z_limit) ** 2 ) limit_xy2 = min(limit_xy2, allowed_radius**2) if end_xy2 > limit_xy2 or end_z > self.max_z or end_z < self.min_z: # Move out of range - verify not a homing move - if (end_pos[:2] != self.home_position[:2] - or end_z < self.min_z or end_z > self.home_position[2]): + if ( + end_pos[:2] != self.home_position[:2] + or end_z < self.min_z + or end_z > self.home_position[2] + ): raise move.move_error() - limit_xy2 = -1. + limit_xy2 = -1.0 if move.axes_d[2]: z_ratio = move.move_d / abs(move.axes_d[2]) - move.limit_speed(self.max_z_velocity * z_ratio, - self.max_z_accel * z_ratio) - limit_xy2 = -1. + move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + limit_xy2 = -1.0 # Limit the speed/accel of this move if is is at the extreme # end of the build envelope - extreme_xy2 = max(end_xy2, move.start_pos[0]**2 + move.start_pos[1]**2) + extreme_xy2 = max(end_xy2, move.start_pos[0] ** 2 + move.start_pos[1] ** 2) if extreme_xy2 > self.slow_xy2: r = 0.5 if extreme_xy2 > self.very_slow_xy2: r = 0.25 move.limit_speed(self.max_velocity * r, self.max_accel * r) - limit_xy2 = -1. + limit_xy2 = -1.0 self.limit_xy2 = min(limit_xy2, self.slow_xy2) + def get_status(self, eventtime): return { - 'homed_axes': '' if self.need_home else 'xyz', - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max, - 'cone_start_z': self.limit_z, + "homed_axes": "" if self.need_home else "xyz", + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, + "cone_start_z": self.limit_z, } + def get_calibration(self): - endstops = [rail.get_homing_info().position_endstop - for rail in self.rails] - stepdists = [rail.get_steppers()[0].get_step_dist() - for rail in self.rails] - return DeltaCalibration(self.radius, self.angles, self.arm_lengths, - endstops, stepdists) + endstops = [rail.get_homing_info().position_endstop for rail in self.rails] + stepdists = [rail.get_steppers()[0].get_step_dist() for rail in self.rails] + return DeltaCalibration( + self.radius, self.angles, self.arm_lengths, endstops, stepdists + ) + # Delta parameter calibration for DELTA_CALIBRATE tool class DeltaCalibration: @@ -176,67 +212,94 @@ class DeltaCalibration: self.stepdists = stepdists # Calculate the XY cartesian coordinates of the delta towers radian_angles = [math.radians(a) for a in angles] - self.towers = [(math.cos(a) * radius, math.sin(a) * radius) - for a in radian_angles] + self.towers = [ + (math.cos(a) * radius, math.sin(a) * radius) for a in radian_angles + ] # Calculate the absolute Z height of each tower endstop radius2 = radius**2 - self.abs_endstops = [e + math.sqrt(a**2 - radius2) - for e, a in zip(endstops, arms)] + self.abs_endstops = [ + e + math.sqrt(a**2 - radius2) for e, a in zip(endstops, arms) + ] + def coordinate_descent_params(self, is_extended): # Determine adjustment parameters (for use with coordinate_descent) - adj_params = ('radius', 'angle_a', 'angle_b', - 'endstop_a', 'endstop_b', 'endstop_c') + adj_params = ( + "radius", + "angle_a", + "angle_b", + "endstop_a", + "endstop_b", + "endstop_c", + ) if is_extended: - adj_params += ('arm_a', 'arm_b', 'arm_c') - params = { 'radius': self.radius } - for i, axis in enumerate('abc'): - params['angle_'+axis] = self.angles[i] - params['arm_'+axis] = self.arms[i] - params['endstop_'+axis] = self.endstops[i] - params['stepdist_'+axis] = self.stepdists[i] + adj_params += ("arm_a", "arm_b", "arm_c") + params = {"radius": self.radius} + for i, axis in enumerate("abc"): + params["angle_" + axis] = self.angles[i] + params["arm_" + axis] = self.arms[i] + params["endstop_" + axis] = self.endstops[i] + params["stepdist_" + axis] = self.stepdists[i] return adj_params, params + def new_calibration(self, params): # Create a new calibration object from coordinate_descent params - radius = params['radius'] - angles = [params['angle_'+a] for a in 'abc'] - arms = [params['arm_'+a] for a in 'abc'] - endstops = [params['endstop_'+a] for a in 'abc'] - stepdists = [params['stepdist_'+a] for a in 'abc'] + radius = params["radius"] + angles = [params["angle_" + a] for a in "abc"] + arms = [params["arm_" + a] for a in "abc"] + endstops = [params["endstop_" + a] for a in "abc"] + stepdists = [params["stepdist_" + a] for a in "abc"] return DeltaCalibration(radius, angles, arms, endstops, stepdists) + def get_position_from_stable(self, stable_position): # Return cartesian coordinates for the given stable_position sphere_coords = [ (t[0], t[1], es - sp * sd) - for sd, t, es, sp in zip(self.stepdists, self.towers, - self.abs_endstops, stable_position) ] + for sd, t, es, sp in zip( + self.stepdists, self.towers, self.abs_endstops, stable_position + ) + ] return mathutil.trilateration(sphere_coords, [a**2 for a in self.arms]) + def calc_stable_position(self, coord): # Return a stable_position from a cartesian coordinate steppos = [ - math.sqrt(a**2 - (t[0]-coord[0])**2 - (t[1]-coord[1])**2) + coord[2] - for t, a in zip(self.towers, self.arms) ] - return [(ep - sp) / sd - for sd, ep, sp in zip(self.stepdists, - self.abs_endstops, steppos)] + math.sqrt(a**2 - (t[0] - coord[0]) ** 2 - (t[1] - coord[1]) ** 2) + coord[2] + for t, a in zip(self.towers, self.arms) + ] + return [ + (ep - sp) / sd + for sd, ep, sp in zip(self.stepdists, self.abs_endstops, steppos) + ] + def save_state(self, configfile): # Save the current parameters (for use with SAVE_CONFIG) - configfile.set('printer', 'delta_radius', "%.6f" % (self.radius,)) - for i, axis in enumerate('abc'): - configfile.set('stepper_'+axis, 'angle', "%.6f" % (self.angles[i],)) - configfile.set('stepper_'+axis, 'arm_length', - "%.6f" % (self.arms[i],)) - configfile.set('stepper_'+axis, 'position_endstop', - "%.6f" % (self.endstops[i],)) + configfile.set("printer", "delta_radius", "%.6f" % (self.radius,)) + for i, axis in enumerate("abc"): + configfile.set("stepper_" + axis, "angle", "%.6f" % (self.angles[i],)) + configfile.set("stepper_" + axis, "arm_length", "%.6f" % (self.arms[i],)) + configfile.set( + "stepper_" + axis, "position_endstop", "%.6f" % (self.endstops[i],) + ) gcode = configfile.get_printer().lookup_object("gcode") gcode.respond_info( "stepper_a: position_endstop: %.6f angle: %.6f arm_length: %.6f\n" "stepper_b: position_endstop: %.6f angle: %.6f arm_length: %.6f\n" "stepper_c: position_endstop: %.6f angle: %.6f arm_length: %.6f\n" "delta_radius: %.6f" - % (self.endstops[0], self.angles[0], self.arms[0], - self.endstops[1], self.angles[1], self.arms[1], - self.endstops[2], self.angles[2], self.arms[2], - self.radius)) + % ( + self.endstops[0], + self.angles[0], + self.arms[0], + self.endstops[1], + self.angles[1], + self.arms[1], + self.endstops[2], + self.angles[2], + self.arms[2], + self.radius, + ) + ) + def load_kinematics(toolhead, config): return DeltaKinematics(toolhead, config) diff --git a/klippy/kinematics/deltesian.py b/klippy/kinematics/deltesian.py index 54b013a5..c2aec720 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -7,50 +7,51 @@ import math, logging import stepper # Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO -SLOW_RATIO = 3. +SLOW_RATIO = 3.0 # Minimum angle with the horizontal for the arm to not exceed - in degrees -MIN_ANGLE = 5. +MIN_ANGLE = 5.0 + class DeltesianKinematics: def __init__(self, toolhead, config): self.rails = [None] * 3 - stepper_configs = [config.getsection('stepper_' + s) - for s in ['left', 'right', 'y']] + stepper_configs = [ + config.getsection("stepper_" + s) for s in ["left", "right", "y"] + ] self.rails[0] = stepper.LookupRail( - stepper_configs[0], need_position_minmax = False) + stepper_configs[0], need_position_minmax=False + ) def_pos_es = self.rails[0].get_homing_info().position_endstop self.rails[1] = stepper.LookupRail( - stepper_configs[1], need_position_minmax = False, - default_position_endstop = def_pos_es) + stepper_configs[1], + need_position_minmax=False, + default_position_endstop=def_pos_es, + ) self.rails[2] = stepper.LookupMultiRail(stepper_configs[2]) arm_x = self.arm_x = [None] * 2 - arm_x[0] = stepper_configs[0].getfloat('arm_x_length', above=0.) - arm_x[1] = stepper_configs[1].getfloat('arm_x_length', arm_x[0], - above=0.) + arm_x[0] = stepper_configs[0].getfloat("arm_x_length", above=0.0) + arm_x[1] = stepper_configs[1].getfloat("arm_x_length", arm_x[0], above=0.0) arm = [None] * 2 - arm[0] = stepper_configs[0].getfloat('arm_length', above=arm_x[0]) - arm[1] = stepper_configs[1].getfloat('arm_length', arm[0], - above=arm_x[1]) + arm[0] = stepper_configs[0].getfloat("arm_length", above=arm_x[0]) + arm[1] = stepper_configs[1].getfloat("arm_length", arm[0], above=arm_x[1]) arm2 = self.arm2 = [a**2 for a in arm] - self.rails[0].setup_itersolve( - 'deltesian_stepper_alloc', arm2[0], -arm_x[0]) - self.rails[1].setup_itersolve( - 'deltesian_stepper_alloc', arm2[1], arm_x[1]) - self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'y') + self.rails[0].setup_itersolve("deltesian_stepper_alloc", arm2[0], -arm_x[0]) + self.rails[1].setup_itersolve("deltesian_stepper_alloc", arm2[1], arm_x[1]) + self.rails[2].setup_itersolve("cartesian_stepper_alloc", b"y") for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) self.limits = [(1.0, -1.0)] * 3 # X axis limits - min_angle = config.getfloat('min_angle', MIN_ANGLE, - minval=0., maxval=90.) + min_angle = config.getfloat("min_angle", MIN_ANGLE, minval=0.0, maxval=90.0) cos_angle = math.cos(math.radians(min_angle)) - x_kin_min = math.ceil( -min(arm_x[0], cos_angle * arm[1] - arm_x[1])) - x_kin_max = math.floor( min(arm_x[1], cos_angle * arm[0] - arm_x[0])) + x_kin_min = math.ceil(-min(arm_x[0], cos_angle * arm[1] - arm_x[1])) + x_kin_max = math.floor(min(arm_x[1], cos_angle * arm[0] - arm_x[0])) x_kin_range = min(x_kin_max - x_kin_min, x_kin_max * 2, -x_kin_min * 2) - print_width = config.getfloat('print_width', None, minval=0., - maxval=x_kin_range) + print_width = config.getfloat( + "print_width", None, minval=0.0, maxval=x_kin_range + ) if print_width: self.limits[0] = (-print_width * 0.5, print_width * 0.5) else: @@ -59,37 +60,56 @@ class DeltesianKinematics: self.limits[1] = self.rails[2].get_range() # Z axis limits pmax = [r.get_homing_info().position_endstop for r in self.rails[:2]] - self._abs_endstop = [p + math.sqrt(a2 - ax**2) for p, a2, ax - in zip( pmax, arm2, arm_x )] + self._abs_endstop = [ + p + math.sqrt(a2 - ax**2) for p, a2, ax in zip(pmax, arm2, arm_x) + ] self.home_z = self._actuator_to_cartesian(self._abs_endstop)[1] z_max = min([self._pillars_z_max(x) for x in self.limits[0]]) - z_min = config.getfloat('minimum_z_position', 0, maxval=z_max) + z_min = config.getfloat("minimum_z_position", 0, maxval=z_max) self.limits[2] = (z_min, z_max) # Limit the speed/accel if is is at the extreme values of the x axis - slow_ratio = config.getfloat('slow_ratio', SLOW_RATIO, minval=0.) + slow_ratio = config.getfloat("slow_ratio", SLOW_RATIO, minval=0.0) self.slow_x2 = self.very_slow_x2 = None - if slow_ratio > 0.: - sr2 = slow_ratio ** 2 - self.slow_x2 = min([math.sqrt((sr2 * a2) / (sr2 + 1)) - - axl for a2, axl in zip(arm2, arm_x)]) ** 2 - self.very_slow_x2 = min([math.sqrt((2 * sr2 * a2) / (2 * sr2 + 1)) - - axl for a2, axl in zip(arm2, arm_x)]) ** 2 - logging.info("Deltesian kinematics: moves slowed past %.2fmm" - " and %.2fmm" - % (math.sqrt(self.slow_x2), - math.sqrt(self.very_slow_x2))) + if slow_ratio > 0.0: + sr2 = slow_ratio**2 + self.slow_x2 = ( + min( + [ + math.sqrt((sr2 * a2) / (sr2 + 1)) - axl + for a2, axl in zip(arm2, arm_x) + ] + ) + ** 2 + ) + self.very_slow_x2 = ( + min( + [ + math.sqrt((2 * sr2 * a2) / (2 * sr2 + 1)) - axl + for a2, axl in zip(arm2, arm_x) + ] + ) + ** 2 + ) + logging.info( + "Deltesian kinematics: moves slowed past %.2fmm" + " and %.2fmm" % (math.sqrt(self.slow_x2), math.sqrt(self.very_slow_x2)) + ) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() - self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, - above=0., maxval=max_velocity) - self.max_z_accel = config.getfloat('max_z_accel', max_accel, - above=0., maxval=max_accel) - self.axes_min = toolhead.Coord(*[l[0] for l in self.limits], e=0.) - self.axes_max = toolhead.Coord(*[l[1] for l in self.limits], e=0.) + self.max_z_velocity = config.getfloat( + "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity + ) + self.max_z_accel = config.getfloat( + "max_z_accel", max_accel, above=0.0, maxval=max_accel + ) + self.axes_min = toolhead.Coord(*[l[0] for l in self.limits], e=0.0) + self.axes_max = toolhead.Coord(*[l[1] for l in self.limits], e=0.0) self.homed_axis = [False] * 3 - self.set_position([0., 0., 0.], "") + self.set_position([0.0, 0.0, 0.0], "") + def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] + def _actuator_to_cartesian(self, sp): arm_x, arm2 = self.arm_x, self.arm2 dx, dz = sum(arm_x), sp[1] - sp[0] @@ -101,38 +121,48 @@ class DeltesianKinematics: x = xt * dx / pivots + zt * dz / pivots - arm_x[0] z = xt * dz / pivots - zt * dx / pivots + sp[0] return [x, z] + def _pillars_z_max(self, x): arm_x, arm2 = self.arm_x, self.arm2 - dz = (math.sqrt(arm2[0] - (arm_x[0] + x)**2), - math.sqrt(arm2[1] - (arm_x[1] - x)**2)) + dz = ( + math.sqrt(arm2[0] - (arm_x[0] + x) ** 2), + math.sqrt(arm2[1] - (arm_x[1] - x) ** 2), + ) return min([o - z for o, z in zip(self._abs_endstop, dz)]) + def calc_position(self, stepper_positions): sp = [stepper_positions[rail.get_name()] for rail in self.rails] x, z = self._actuator_to_cartesian(sp[:2]) return [x, sp[2], z] + def set_position(self, newpos, homing_axes): for rail in self.rails: rail.set_position(newpos) for axis_name in homing_axes: axis = "xyz".index(axis_name) self.homed_axis[axis] = True + def clear_homing_state(self, clear_axes): for axis, axis_name in enumerate("xyz"): if axis_name in clear_axes: self.homed_axis[axis] = False + def home(self, homing_state): homing_axes = homing_state.get_axes() home_xz = 0 in homing_axes or 2 in homing_axes home_y = 1 in homing_axes - forceaxes = ([0, 1, 2] if (home_xz and home_y) else - [0, 2] if home_xz else [1] if home_y else []) + forceaxes = ( + [0, 1, 2] + if (home_xz and home_y) + else [0, 2] if home_xz else [1] if home_y else [] + ) homing_state.set_axes(forceaxes) homepos = [None] * 4 if home_xz: homing_state.set_axes([0, 1, 2] if home_y else [0, 2]) - homepos[0], homepos[2] = 0., self.home_z + homepos[0], homepos[2] = 0.0, self.home_z forcepos = list(homepos) - dz2 = [(a2 - ax ** 2) for a2, ax in zip(self.arm2, self.arm_x)] + dz2 = [(a2 - ax**2) for a2, ax in zip(self.arm2, self.arm_x)] forcepos[2] = -1.5 * math.sqrt(max(dz2)) homing_state.home_rails(self.rails[:2], forcepos, homepos) if home_y: @@ -145,11 +175,12 @@ class DeltesianKinematics: else: forcepos[1] += 1.5 * (position_max - hi.position_endstop) homing_state.home_rails([self.rails[2]], forcepos, homepos) + def check_move(self, move): limits = list(map(list, self.limits)) spos, epos = move.start_pos, move.end_pos homing_move = False - if epos[0] == 0. and epos[2] == self.home_z and not move.axes_d[1]: + if epos[0] == 0.0 and epos[2] == self.home_z and not move.axes_d[1]: # Identify a homing move homing_move = True elif epos[2] > limits[2][1]: @@ -164,22 +195,23 @@ class DeltesianKinematics: raise move.move_error() if move.axes_d[2]: z_ratio = move.move_d / abs(move.axes_d[2]) - move.limit_speed( - self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) # Limit the speed/accel if is is at the extreme values of the x axis if move.axes_d[0] and self.slow_x2 and self.very_slow_x2: move_x2 = max(spos[0] ** 2, epos[0] ** 2) if move_x2 > self.very_slow_x2: - move.limit_speed(self.max_velocity *0.25, self.max_accel *0.25) + move.limit_speed(self.max_velocity * 0.25, self.max_accel * 0.25) elif move_x2 > self.slow_x2: - move.limit_speed(self.max_velocity *0.50, self.max_accel *0.50) + move.limit_speed(self.max_velocity * 0.50, self.max_accel * 0.50) + def get_status(self, eventtime): axes = [a for a, b in zip("xyz", self.homed_axis) if b] return { - 'homed_axes': "".join(axes), - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max, + "homed_axes": "".join(axes), + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, } + def load_kinematics(toolhead, config): return DeltesianKinematics(toolhead, config) diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index 58ccdbec..25ac394d 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -6,51 +6,75 @@ import math, logging import stepper, chelper + class ExtruderStepper: def __init__(self, config): self.printer = config.get_printer() self.name = config.get_name().split()[-1] - self.pressure_advance = self.pressure_advance_smooth_time = 0. - self.config_pa = config.getfloat('pressure_advance', 0., minval=0.) + self.pressure_advance = self.pressure_advance_smooth_time = 0.0 + self.config_pa = config.getfloat("pressure_advance", 0.0, minval=0.0) self.config_smooth_time = config.getfloat( - 'pressure_advance_smooth_time', 0.040, above=0., maxval=.200) + "pressure_advance_smooth_time", 0.040, above=0.0, maxval=0.200 + ) # Setup stepper self.stepper = stepper.PrinterStepper(config) ffi_main, ffi_lib = chelper.get_ffi() - self.sk_extruder = ffi_main.gc(ffi_lib.extruder_stepper_alloc(), - ffi_lib.extruder_stepper_free) + self.sk_extruder = ffi_main.gc( + ffi_lib.extruder_stepper_alloc(), ffi_lib.extruder_stepper_free + ) self.stepper.set_stepper_kinematics(self.sk_extruder) self.motion_queue = None # Register commands - self.printer.register_event_handler("klippy:connect", - self._handle_connect) - gcode = self.printer.lookup_object('gcode') - if self.name == 'extruder': - gcode.register_mux_command("SET_PRESSURE_ADVANCE", "EXTRUDER", None, - self.cmd_default_SET_PRESSURE_ADVANCE, - desc=self.cmd_SET_PRESSURE_ADVANCE_help) - gcode.register_mux_command("SET_PRESSURE_ADVANCE", "EXTRUDER", - self.name, self.cmd_SET_PRESSURE_ADVANCE, - desc=self.cmd_SET_PRESSURE_ADVANCE_help) - gcode.register_mux_command("SET_EXTRUDER_ROTATION_DISTANCE", "EXTRUDER", - self.name, self.cmd_SET_E_ROTATION_DISTANCE, - desc=self.cmd_SET_E_ROTATION_DISTANCE_help) - gcode.register_mux_command("SYNC_EXTRUDER_MOTION", "EXTRUDER", - self.name, self.cmd_SYNC_EXTRUDER_MOTION, - desc=self.cmd_SYNC_EXTRUDER_MOTION_help) + self.printer.register_event_handler("klippy:connect", self._handle_connect) + gcode = self.printer.lookup_object("gcode") + if self.name == "extruder": + gcode.register_mux_command( + "SET_PRESSURE_ADVANCE", + "EXTRUDER", + None, + self.cmd_default_SET_PRESSURE_ADVANCE, + desc=self.cmd_SET_PRESSURE_ADVANCE_help, + ) + gcode.register_mux_command( + "SET_PRESSURE_ADVANCE", + "EXTRUDER", + self.name, + self.cmd_SET_PRESSURE_ADVANCE, + desc=self.cmd_SET_PRESSURE_ADVANCE_help, + ) + gcode.register_mux_command( + "SET_EXTRUDER_ROTATION_DISTANCE", + "EXTRUDER", + self.name, + self.cmd_SET_E_ROTATION_DISTANCE, + desc=self.cmd_SET_E_ROTATION_DISTANCE_help, + ) + gcode.register_mux_command( + "SYNC_EXTRUDER_MOTION", + "EXTRUDER", + self.name, + self.cmd_SYNC_EXTRUDER_MOTION, + desc=self.cmd_SYNC_EXTRUDER_MOTION_help, + ) + def _handle_connect(self): - toolhead = self.printer.lookup_object('toolhead') + 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) + def get_status(self, eventtime): - return {'pressure_advance': self.pressure_advance, - 'smooth_time': self.pressure_advance_smooth_time, - 'motion_queue': self.motion_queue} + return { + "pressure_advance": self.pressure_advance, + "smooth_time": self.pressure_advance_smooth_time, + "motion_queue": self.motion_queue, + } + def find_past_position(self, print_time): mcu_pos = self.stepper.get_past_mcu_position(print_time) return self.stepper.mcu_to_commanded_position(mcu_pos) + def sync_to_extruder(self, extruder_name): - toolhead = self.printer.lookup_object('toolhead') + toolhead = self.printer.lookup_object("toolhead") toolhead.flush_step_generation() if not extruder_name: self.stepper.set_trapq(None) @@ -58,62 +82,72 @@ class ExtruderStepper: return extruder = self.printer.lookup_object(extruder_name, None) if extruder is None or not isinstance(extruder, PrinterExtruder): - raise self.printer.command_error("'%s' is not a valid extruder." - % (extruder_name,)) - self.stepper.set_position([extruder.last_position, 0., 0.]) + raise self.printer.command_error( + "'%s' is not a valid extruder." % (extruder_name,) + ) + self.stepper.set_position([extruder.last_position, 0.0, 0.0]) self.stepper.set_trapq(extruder.get_trapq()) self.motion_queue = extruder_name + def _set_pressure_advance(self, pressure_advance, smooth_time): old_smooth_time = self.pressure_advance_smooth_time if not self.pressure_advance: - old_smooth_time = 0. + old_smooth_time = 0.0 new_smooth_time = smooth_time if not pressure_advance: - new_smooth_time = 0. + new_smooth_time = 0.0 toolhead = self.printer.lookup_object("toolhead") if new_smooth_time != old_smooth_time: toolhead.note_step_generation_scan_time( - new_smooth_time * .5, old_delay=old_smooth_time * .5) + new_smooth_time * 0.5, old_delay=old_smooth_time * 0.5 + ) ffi_main, ffi_lib = chelper.get_ffi() espa = ffi_lib.extruder_set_pressure_advance toolhead.register_lookahead_callback( - lambda print_time: espa(self.sk_extruder, print_time, - pressure_advance, new_smooth_time)) + lambda print_time: espa( + self.sk_extruder, print_time, pressure_advance, new_smooth_time + ) + ) self.pressure_advance = pressure_advance self.pressure_advance_smooth_time = smooth_time + cmd_SET_PRESSURE_ADVANCE_help = "Set pressure advance parameters" + def cmd_default_SET_PRESSURE_ADVANCE(self, gcmd): - extruder = self.printer.lookup_object('toolhead').get_extruder() + extruder = self.printer.lookup_object("toolhead").get_extruder() if extruder.extruder_stepper is None: raise gcmd.error("Active extruder does not have a stepper") strapq = extruder.extruder_stepper.stepper.get_trapq() if strapq is not extruder.get_trapq(): raise gcmd.error("Unable to infer active extruder stepper") extruder.extruder_stepper.cmd_SET_PRESSURE_ADVANCE(gcmd) + def cmd_SET_PRESSURE_ADVANCE(self, gcmd): - pressure_advance = gcmd.get_float('ADVANCE', self.pressure_advance, - minval=0.) - smooth_time = gcmd.get_float('SMOOTH_TIME', - self.pressure_advance_smooth_time, - minval=0., maxval=.200) + pressure_advance = gcmd.get_float("ADVANCE", self.pressure_advance, minval=0.0) + smooth_time = gcmd.get_float( + "SMOOTH_TIME", self.pressure_advance_smooth_time, minval=0.0, maxval=0.200 + ) self._set_pressure_advance(pressure_advance, smooth_time) - msg = ("pressure_advance: %.6f\n" - "pressure_advance_smooth_time: %.6f" - % (pressure_advance, smooth_time)) + msg = "pressure_advance: %.6f\n" "pressure_advance_smooth_time: %.6f" % ( + pressure_advance, + smooth_time, + ) self.printer.set_rollover_info(self.name, "%s: %s" % (self.name, msg)) gcmd.respond_info(msg, log=False) + cmd_SET_E_ROTATION_DISTANCE_help = "Set extruder rotation distance" + def cmd_SET_E_ROTATION_DISTANCE(self, gcmd): - rotation_dist = gcmd.get_float('DISTANCE', None) + rotation_dist = gcmd.get_float("DISTANCE", None) if rotation_dist is not None: if not rotation_dist: raise gcmd.error("Rotation distance can not be zero") invert_dir, orig_invert_dir = self.stepper.get_dir_inverted() next_invert_dir = orig_invert_dir - if rotation_dist < 0.: + if rotation_dist < 0.0: next_invert_dir = not orig_invert_dir rotation_dist = -rotation_dist - toolhead = self.printer.lookup_object('toolhead') + toolhead = self.printer.lookup_object("toolhead") toolhead.flush_step_generation() self.stepper.set_rotation_distance(rotation_dist) self.stepper.set_dir_inverted(next_invert_dir) @@ -122,48 +156,53 @@ class ExtruderStepper: invert_dir, orig_invert_dir = self.stepper.get_dir_inverted() if invert_dir != orig_invert_dir: rotation_dist = -rotation_dist - gcmd.respond_info("Extruder '%s' rotation distance set to %0.6f" - % (self.name, rotation_dist)) + gcmd.respond_info( + "Extruder '%s' rotation distance set to %0.6f" % (self.name, rotation_dist) + ) + cmd_SYNC_EXTRUDER_MOTION_help = "Set extruder stepper motion queue" + def cmd_SYNC_EXTRUDER_MOTION(self, gcmd): - ename = gcmd.get('MOTION_QUEUE') + ename = gcmd.get("MOTION_QUEUE") self.sync_to_extruder(ename) - gcmd.respond_info("Extruder '%s' now syncing with '%s'" - % (self.name, ename)) + gcmd.respond_info("Extruder '%s' now syncing with '%s'" % (self.name, ename)) + # Tracking for hotend heater, extrusion motion queue, and extruder stepper class PrinterExtruder: def __init__(self, config, extruder_num): self.printer = config.get_printer() self.name = config.get_name() - self.last_position = 0. + self.last_position = 0.0 # Setup hotend heater - pheaters = self.printer.load_object(config, 'heaters') - gcode_id = 'T%d' % (extruder_num,) + pheaters = self.printer.load_object(config, "heaters") + gcode_id = "T%d" % (extruder_num,) self.heater = pheaters.setup_heater(config, gcode_id) # Setup kinematic checks - self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.) + self.nozzle_diameter = config.getfloat("nozzle_diameter", above=0.0) filament_diameter = config.getfloat( - 'filament_diameter', minval=self.nozzle_diameter) - self.filament_area = math.pi * (filament_diameter * .5)**2 - def_max_cross_section = 4. * self.nozzle_diameter**2 + "filament_diameter", minval=self.nozzle_diameter + ) + self.filament_area = math.pi * (filament_diameter * 0.5) ** 2 + def_max_cross_section = 4.0 * self.nozzle_diameter**2 def_max_extrude_ratio = def_max_cross_section / self.filament_area max_cross_section = config.getfloat( - 'max_extrude_cross_section', def_max_cross_section, above=0.) + "max_extrude_cross_section", def_max_cross_section, above=0.0 + ) self.max_extrude_ratio = max_cross_section / self.filament_area logging.info("Extruder max_extrude_ratio=%.6f", self.max_extrude_ratio) - toolhead = self.printer.lookup_object('toolhead') + toolhead = self.printer.lookup_object("toolhead") max_velocity, max_accel = toolhead.get_max_velocity() self.max_e_velocity = config.getfloat( - 'max_extrude_only_velocity', max_velocity * def_max_extrude_ratio - , above=0.) + "max_extrude_only_velocity", max_velocity * def_max_extrude_ratio, above=0.0 + ) self.max_e_accel = config.getfloat( - 'max_extrude_only_accel', max_accel * def_max_extrude_ratio - , above=0.) - self.max_e_dist = config.getfloat( - 'max_extrude_only_distance', 50., minval=0.) + "max_extrude_only_accel", max_accel * def_max_extrude_ratio, above=0.0 + ) + self.max_e_dist = config.getfloat("max_extrude_only_distance", 50.0, minval=0.0) self.instant_corner_v = config.getfloat( - 'instantaneous_corner_velocity', 1., minval=0.) + "instantaneous_corner_velocity", 1.0, minval=0.0 + ) # Setup extruder trapq (trapezoidal motion queue) ffi_main, ffi_lib = chelper.get_ffi() self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) @@ -171,111 +210,151 @@ class PrinterExtruder: self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves # Setup extruder stepper self.extruder_stepper = None - if (config.get('step_pin', None) is not None - or config.get('dir_pin', None) is not None - or config.get('rotation_distance', None) is not None): + if ( + config.get("step_pin", None) is not None + or config.get("dir_pin", None) is not None + or config.get("rotation_distance", None) is not None + ): self.extruder_stepper = ExtruderStepper(config) self.extruder_stepper.stepper.set_trapq(self.trapq) # Register commands - gcode = self.printer.lookup_object('gcode') - if self.name == 'extruder': - toolhead.set_extruder(self, 0.) + gcode = self.printer.lookup_object("gcode") + if self.name == "extruder": + toolhead.set_extruder(self, 0.0) gcode.register_command("M104", self.cmd_M104) gcode.register_command("M109", self.cmd_M109) - gcode.register_mux_command("ACTIVATE_EXTRUDER", "EXTRUDER", - self.name, self.cmd_ACTIVATE_EXTRUDER, - desc=self.cmd_ACTIVATE_EXTRUDER_help) + gcode.register_mux_command( + "ACTIVATE_EXTRUDER", + "EXTRUDER", + self.name, + self.cmd_ACTIVATE_EXTRUDER, + desc=self.cmd_ACTIVATE_EXTRUDER_help, + ) + def get_status(self, eventtime): sts = self.heater.get_status(eventtime) - sts['can_extrude'] = self.heater.can_extrude + sts["can_extrude"] = self.heater.can_extrude if self.extruder_stepper is not None: sts.update(self.extruder_stepper.get_status(eventtime)) return sts + def get_name(self): return self.name + def get_heater(self): return self.heater + def get_trapq(self): return self.trapq + def get_axis_gcode_id(self): - return 'E' + return "E" + def stats(self, eventtime): return self.heater.stats(eventtime) + def check_move(self, move, ea_index): if not self.heater.can_extrude: raise self.printer.command_error( "Extrude below minimum temp\n" - "See the 'min_extrude_temp' config option for details") + "See the 'min_extrude_temp' config option for details" + ) axis_r = move.axes_r[ea_index] axis_d = move.axes_d[ea_index] - if (not move.axes_d[0] and not move.axes_d[1]) or axis_r < 0.: + if (not move.axes_d[0] and not move.axes_d[1]) or axis_r < 0.0: # Extrude only move (or retraction move) - limit accel and velocity if abs(axis_d) > self.max_e_dist: raise self.printer.command_error( "Extrude only move too long (%.3fmm vs %.3fmm)\n" "See the 'max_extrude_only_distance' config" - " option for details" % (axis_d, self.max_e_dist)) - inv_extrude_r = 1. / abs(axis_r) - move.limit_speed(self.max_e_velocity * inv_extrude_r, - self.max_e_accel * inv_extrude_r) + " option for details" % (axis_d, self.max_e_dist) + ) + inv_extrude_r = 1.0 / abs(axis_r) + move.limit_speed( + self.max_e_velocity * inv_extrude_r, self.max_e_accel * inv_extrude_r + ) elif axis_r > self.max_extrude_ratio: if axis_d <= self.nozzle_diameter * self.max_extrude_ratio: # Permit extrusion if amount extruded is tiny return area = axis_r * self.filament_area - logging.debug("Overextrude: %s vs %s (area=%.3f dist=%.3f)", - axis_r, self.max_extrude_ratio, area, move.move_d) + logging.debug( + "Overextrude: %s vs %s (area=%.3f dist=%.3f)", + axis_r, + self.max_extrude_ratio, + area, + move.move_d, + ) raise self.printer.command_error( "Move exceeds maximum extrusion (%.3fmm^2 vs %.3fmm^2)\n" "See the 'max_extrude_cross_section' config option for details" - % (area, self.max_extrude_ratio * self.filament_area)) + % (area, self.max_extrude_ratio * self.filament_area) + ) + def calc_junction(self, prev_move, move, ea_index): diff_r = move.axes_r[ea_index] - prev_move.axes_r[ea_index] if diff_r: - return (self.instant_corner_v / abs(diff_r))**2 + return (self.instant_corner_v / abs(diff_r)) ** 2 return move.max_cruise_v2 + def process_move(self, print_time, move, ea_index): axis_r = move.axes_r[ea_index] accel = move.accel * axis_r start_v = move.start_v * axis_r cruise_v = move.cruise_v * axis_r can_pressure_advance = False - if axis_r > 0. and (move.axes_d[0] or move.axes_d[1]): + if axis_r > 0.0 and (move.axes_d[0] or move.axes_d[1]): can_pressure_advance = True # Queue movement (x is extruder movement, y is pressure advance flag) - self.trapq_append(self.trapq, print_time, - move.accel_t, move.cruise_t, move.decel_t, - move.start_pos[ea_index], 0., 0., - 1., can_pressure_advance, 0., - start_v, cruise_v, accel) + self.trapq_append( + self.trapq, + print_time, + move.accel_t, + move.cruise_t, + move.decel_t, + move.start_pos[ea_index], + 0.0, + 0.0, + 1.0, + can_pressure_advance, + 0.0, + start_v, + cruise_v, + accel, + ) self.last_position = move.end_pos[ea_index] + def find_past_position(self, print_time): if self.extruder_stepper is None: - return 0. + return 0.0 return self.extruder_stepper.find_past_position(print_time) + def cmd_M104(self, gcmd, wait=False): # Set Extruder Temperature - temp = gcmd.get_float('S', 0.) - index = gcmd.get_int('T', None, minval=0) + temp = gcmd.get_float("S", 0.0) + index = gcmd.get_int("T", None, minval=0) if index is not None: - section = 'extruder' + section = "extruder" if index: - section = 'extruder%d' % (index,) + section = "extruder%d" % (index,) extruder = self.printer.lookup_object(section, None) if extruder is None: - if temp <= 0.: + if temp <= 0.0: return raise gcmd.error("Extruder not configured") else: - extruder = self.printer.lookup_object('toolhead').get_extruder() - pheaters = self.printer.lookup_object('heaters') + extruder = self.printer.lookup_object("toolhead").get_extruder() + pheaters = self.printer.lookup_object("heaters") pheaters.set_temperature(extruder.get_heater(), temp, wait) + def cmd_M109(self, gcmd): # Set Extruder Temperature and Wait self.cmd_M104(gcmd, wait=True) + cmd_ACTIVATE_EXTRUDER_help = "Change the active extruder" + def cmd_ACTIVATE_EXTRUDER(self, gcmd): - toolhead = self.printer.lookup_object('toolhead') + toolhead = self.printer.lookup_object("toolhead") if toolhead.get_extruder() is self: gcmd.respond_info("Extruder %s already active" % (self.name,)) return @@ -284,31 +363,40 @@ class PrinterExtruder: toolhead.set_extruder(self, self.last_position) self.printer.send_event("extruder:activate_extruder") + # Dummy extruder class used when a printer has no extruder at all class DummyExtruder: def __init__(self, printer): self.printer = printer + def check_move(self, move, ea_index): raise move.move_error("Extrude when no extruder present") + def find_past_position(self, print_time): - return 0. + return 0.0 + def calc_junction(self, prev_move, move, ea_index): return move.max_cruise_v2 + def get_name(self): return "" + def get_heater(self): raise self.printer.command_error("Extruder not configured") + def get_trapq(self): return None + def get_axis_gcode_id(self): - return 'E' + return "E" + def add_printer_objects(config): printer = config.get_printer() for i in range(99): - section = 'extruder' + section = "extruder" if i: - section = 'extruder%d' % (i,) + section = "extruder%d" % (i,) if not config.has_section(section): break pe = PrinterExtruder(config.getsection(section), i) diff --git a/klippy/kinematics/generic_cartesian.py b/klippy/kinematics/generic_cartesian.py index b8cabb77..a76e4a21 100644 --- a/klippy/kinematics/generic_cartesian.py +++ b/klippy/kinematics/generic_cartesian.py @@ -9,6 +9,7 @@ import gcode, mathutil, stepper from . import idex_modes from . import kinematic_stepper as ks + def mat_mul(a, b): if len(a[0]) != len(b): return None @@ -19,89 +20,118 @@ def mat_mul(a, b): res[i].append(sum(a[i][k] * b[k][j] for k in range(len(b)))) return res + def mat_transp(a): res = [] for i in range(len(a[0])): res.append([a[j][i] for j in range(len(a))]) return res + def mat_pseudo_inverse(m): mt = mat_transp(m) mtm = mat_mul(mt, m) pinv = mat_mul(mathutil.matrix_inv(mtm), mt) return pinv + class MainCarriage: def __init__(self, config, axis): self.rail = stepper.GenericPrinterRail(config) - self.axis = ord(axis) - ord('x') + self.axis = ord(axis) - ord("x") self.axis_name = axis self.dual_carriage = None + def get_name(self): return self.rail.get_name(short=True) + def get_axis(self): return self.axis + def get_rail(self): return self.rail + def add_stepper(self, kin_stepper): self.rail.add_stepper(kin_stepper.get_stepper()) + def is_active(self): if self.dual_carriage is None: return True return self.dual_carriage.get_dc_module().is_active(self.rail) + def set_dual_carriage(self, carriage): old_dc = self.dual_carriage self.dual_carriage = carriage return old_dc + def get_dual_carriage(self): return self.dual_carriage + class ExtraCarriage: def __init__(self, config, carriages): self.name = config.get_name().split()[-1] - self.primary_carriage = config.getchoice('primary_carriage', carriages) - self.endstop_pin = config.get('endstop_pin') + self.primary_carriage = config.getchoice("primary_carriage", carriages) + self.endstop_pin = config.get("endstop_pin") + def get_name(self): return self.name + def get_axis(self): return self.primary_carriage.get_axis() + def get_rail(self): return self.primary_carriage.get_rail() + def add_stepper(self, kin_stepper): - self.get_rail().add_stepper(kin_stepper.get_stepper(), - self.endstop_pin, self.name) + self.get_rail().add_stepper( + kin_stepper.get_stepper(), self.endstop_pin, self.name + ) + class DualCarriage: def __init__(self, config, carriages): self.printer = config.get_printer() self.rail = stepper.GenericPrinterRail(config) - self.primary_carriage = config.getchoice('primary_carriage', carriages) + self.primary_carriage = config.getchoice("primary_carriage", carriages) if self.primary_carriage.set_dual_carriage(self) is not None: raise config.error( - "Redefinition of dual_carriage for carriage '%s'" % - self.primary_carriage.get_name()) + "Redefinition of dual_carriage for carriage '%s'" + % self.primary_carriage.get_name() + ) self.axis = self.primary_carriage.get_axis() if self.axis > 1: - raise config.error("Invalid axis '%s' for dual_carriage" % - self.primary_carriage.get_axis_name()) - self.safe_dist = config.getfloat('safe_distance', None, minval=0.) + raise config.error( + "Invalid axis '%s' for dual_carriage" + % self.primary_carriage.get_axis_name() + ) + self.safe_dist = config.getfloat("safe_distance", None, minval=0.0) + def get_name(self): return self.rail.get_name(short=True) + def get_axis(self): return self.primary_carriage.get_axis() + def get_rail(self): return self.rail + def get_safe_dist(self): return self.safe_dist + def get_dc_module(self): - return self.printer.lookup_object('dual_carriage') + return self.printer.lookup_object("dual_carriage") + def is_active(self): return self.get_dc_module().is_active(self.rail) + def get_dual_carriage(self): return self.primary_carriage + def add_stepper(self, kin_stepper): self.rail.add_stepper(kin_stepper.get_stepper()) + class GenericCartesianKinematics: def __init__(self, toolhead, config): self.printer = config.get_printer() @@ -115,42 +145,50 @@ class GenericCartesianKinematics: primary_rails = [pc.get_rail() for pc in pcs] dual_rails = [dc.get_rail() for dc in self.dc_carriages] axes = [dc.get_axis() for dc in self.dc_carriages] - safe_dist = {dc.get_axis() : dc.get_safe_dist() - for dc in self.dc_carriages} + safe_dist = {dc.get_axis(): dc.get_safe_dist() for dc in self.dc_carriages} self.dc_module = dc_module = idex_modes.DualCarriages( - self.printer, primary_rails, dual_rails, axes, safe_dist) - zero_pos = (0., 0.) + self.printer, primary_rails, dual_rails, axes, safe_dist + ) + zero_pos = (0.0, 0.0) for acs in itertools.product(*zip(pcs, self.dc_carriages)): for c in acs: dc_module.get_dc_rail_wrapper(c.get_rail()).activate( - idex_modes.PRIMARY, zero_pos) + idex_modes.PRIMARY, zero_pos + ) dc_rail = c.get_dual_carriage().get_rail() dc_module.get_dc_rail_wrapper(dc_rail).inactivate(zero_pos) self._check_kinematics(config.error) for c in pcs: dc_module.get_dc_rail_wrapper(c.get_rail()).activate( - idex_modes.PRIMARY, zero_pos) + idex_modes.PRIMARY, zero_pos + ) dc_rail = c.get_dual_carriage().get_rail() dc_module.get_dc_rail_wrapper(dc_rail).inactivate(zero_pos) else: self._check_kinematics(config.error) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() - self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, - above=0., maxval=max_velocity) - self.max_z_accel = config.getfloat('max_z_accel', max_accel, - above=0., maxval=max_accel) + self.max_z_velocity = config.getfloat( + "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity + ) + self.max_z_accel = config.getfloat( + "max_z_accel", max_accel, above=0.0, maxval=max_accel + ) self.limits = [(1.0, -1.0)] * 3 # Register gcode commands - gcode = self.printer.lookup_object('gcode') - gcode.register_command("SET_STEPPER_CARRIAGES", - self.cmd_SET_STEPPER_CARRIAGES, - desc=self.cmd_SET_STEPPER_CARRIAGES_help) + gcode = self.printer.lookup_object("gcode") + gcode.register_command( + "SET_STEPPER_CARRIAGES", + self.cmd_SET_STEPPER_CARRIAGES, + desc=self.cmd_SET_STEPPER_CARRIAGES_help, + ) + def _load_kinematics(self, config): - carriages = {a : MainCarriage(config.getsection('carriage ' + a), a) - for a in 'xyz'} + carriages = { + a: MainCarriage(config.getsection("carriage " + a), a) for a in "xyz" + } dc_carriages = [] - for c in config.get_prefix_sections('dual_carriage '): + for c in config.get_prefix_sections("dual_carriage "): dc_carriages.append(DualCarriage(c, carriages)) for dc in dc_carriages: name = dc.get_name() @@ -160,7 +198,7 @@ class GenericCartesianKinematics: self.carriages = dict(carriages) self.dc_carriages = dc_carriages ec_carriages = [] - for c in config.get_prefix_sections('extra_carriage '): + for c in config.get_prefix_sections("extra_carriage "): ec_carriages.append(ExtraCarriage(c, carriages)) for ec in ec_carriages: name = ec.get_name() @@ -171,6 +209,7 @@ class GenericCartesianKinematics: self.all_carriages = carriages self._check_carriages_references(config.error) self._check_multi_mcu_homing(config.error) + def _check_carriages_references(self, report_error): carriages = dict(self.all_carriages) for s in self.kin_steppers: @@ -178,21 +217,35 @@ class GenericCartesianKinematics: carriages.pop(c.get_name(), None) if carriages: raise report_error( - "Carriage(s) %s must be referenced by some " - "stepper(s)" % (", ".join(carriages),)) + "Carriage(s) %s must be referenced by some " + "stepper(s)" % (", ".join(carriages),) + ) + def _check_multi_mcu_homing(self, report_error): for carriage in self.carriages.values(): for es in carriage.get_rail().get_endstops(): - stepper_mcus = set([s.get_mcu() for s in es[0].get_steppers() - if s in carriage.get_rail().get_steppers()]) + stepper_mcus = set( + [ + s.get_mcu() + for s in es[0].get_steppers() + if s in carriage.get_rail().get_steppers() + ] + ) if len(stepper_mcus) > 1: - raise report_error("Multi-mcu homing not supported on" - " multi-mcu shared carriage %s" % es[1]) + raise report_error( + "Multi-mcu homing not supported on" + " multi-mcu shared carriage %s" % es[1] + ) + def _load_steppers(self, config, carriages): - return [ks.KinematicStepper(c, carriages) - for c in config.get_prefix_sections('stepper ')] + return [ + ks.KinematicStepper(c, carriages) + for c in config.get_prefix_sections("stepper ") + ] + def get_steppers(self): return [s.get_stepper() for s in self.kin_steppers] + def get_primary_carriages(self): carriages = [self.carriages[a] for a in "xyz"] if self.dc_module: @@ -202,13 +255,15 @@ class GenericCartesianKinematics: if c.get_rail() == primary_rail: carriages[a] = c return carriages + def _get_kinematics_coeffs(self): - matr = {s.get_name() : list(s.get_kin_coeffs()) - for s in self.kin_steppers} - offs = {s.get_name() : [0.] * 3 for s in self.kin_steppers} + matr = {s.get_name(): list(s.get_kin_coeffs()) for s in self.kin_steppers} + offs = {s.get_name(): [0.0] * 3 for s in self.kin_steppers} if self.dc_module is None: - return ([matr[s.get_name()] for s in self.kin_steppers], - [0. for s in self.kin_steppers]) + return ( + [matr[s.get_name()] for s in self.kin_steppers], + [0.0 for s in self.kin_steppers], + ) axes = [dc.get_axis() for dc in self.dc_carriages] orig_matr = copy.deepcopy(matr) for dc in self.dc_carriages: @@ -218,33 +273,41 @@ class GenericCartesianKinematics: for s in c.get_rail().get_steppers(): matr[s.get_name()][axis] *= m offs[s.get_name()][axis] += o - return ([matr[s.get_name()] for s in self.kin_steppers], - [mathutil.matrix_dot(orig_matr[s.get_name()], - offs[s.get_name()]) - for s in self.kin_steppers]) + return ( + [matr[s.get_name()] for s in self.kin_steppers], + [ + mathutil.matrix_dot(orig_matr[s.get_name()], offs[s.get_name()]) + for s in self.kin_steppers + ], + ) + def _check_kinematics(self, report_error): matr, _ = self._get_kinematics_coeffs() det = mathutil.matrix_det(mat_mul(mat_transp(matr), matr)) if abs(det) < 0.00001: raise report_error( - "Verify configured stepper(s) and their 'carriages' " - "specifications, the current configuration does not " - "allow independent movements of all printer axes.") + "Verify configured stepper(s) and their 'carriages' " + "specifications, the current configuration does not " + "allow independent movements of all printer axes." + ) + def calc_position(self, stepper_positions): matr, offs = self._get_kinematics_coeffs() spos = [stepper_positions[s.get_name()] for s in self.kin_steppers] pinv = mat_pseudo_inverse(matr) - pos = mat_mul([[sp-o for sp, o in zip(spos, offs)]], mat_transp(pinv)) + pos = mat_mul([[sp - o for sp, o in zip(spos, offs)]], mat_transp(pinv)) for i in range(3): if not any(pinv[i]): pos[0][i] = None return pos[0] + def update_limits(self, i, range): l, h = self.limits[i] # Only update limits if this axis was already homed, # otherwise leave in un-homed state. if l <= h: self.limits[i] = range + def set_position(self, newpos, homing_axes): for s in self.kin_steppers: s.set_position(newpos) @@ -254,10 +317,12 @@ class GenericCartesianKinematics: if c.get_axis() == axis and c.is_active(): self.limits[axis] = c.get_rail().get_range() break + def clear_homing_state(self, clear_axes): for axis, axis_name in enumerate("xyz"): if axis_name in clear_axes: self.limits[axis] = (1.0, -1.0) + def home_axis(self, homing_state, axis, rail): # Determine movement position_min, position_max = rail.get_range() @@ -271,6 +336,7 @@ class GenericCartesianKinematics: forcepos[axis] += 1.5 * (position_max - hi.position_endstop) # Perform homing homing_state.home_rails([rail], forcepos, homepos) + def home(self, homing_state): self._check_kinematics(self.printer.command_error) # Each axis is homed independently and in order @@ -280,20 +346,26 @@ class GenericCartesianKinematics: self.dc_module.home(homing_state, axis) else: self.home_axis(homing_state, axis, carriage.get_rail()) + def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): - if (move.axes_d[i] - and (end_pos[i] < self.limits[i][0] - or end_pos[i] > self.limits[i][1])): + if move.axes_d[i] and ( + end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1] + ): if self.limits[i][0] > self.limits[i][1]: raise move.move_error("Must home axis first") raise move.move_error() + def check_move(self, move): limits = self.limits xpos, ypos = move.end_pos[:2] - if (xpos < limits[0][0] or xpos > limits[0][1] - or ypos < limits[1][0] or ypos > limits[1][1]): + if ( + xpos < limits[0][0] + or xpos > limits[0][1] + or ypos < limits[1][0] + or ypos > limits[1][1] + ): self._check_endstops(move) if not move.axes_d[2]: # Normal XY move - use defaults @@ -301,27 +373,31 @@ class GenericCartesianKinematics: # Move with Z - update velocity and accel for slower Z axis self._check_endstops(move) z_ratio = move.move_d / abs(move.axes_d[2]) - move.limit_speed( - self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + def get_status(self, eventtime): axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h] - ranges = [c.get_rail().get_range() - for c in self.get_primary_carriages()] - axes_min = gcode.Coord(*[r[0] for r in ranges], e=0.) - axes_max = gcode.Coord(*[r[1] for r in ranges], e=0.) + ranges = [c.get_rail().get_range() for c in self.get_primary_carriages()] + axes_min = gcode.Coord(*[r[0] for r in ranges], e=0.0) + axes_max = gcode.Coord(*[r[1] for r in ranges], e=0.0) return { - 'homed_axes': "".join(axes), - 'axis_minimum': axes_min, - 'axis_maximum': axes_max, + "homed_axes": "".join(axes), + "axis_minimum": axes_min, + "axis_maximum": axes_max, } + cmd_SET_STEPPER_CARRIAGES_help = "Set stepper carriages" + def cmd_SET_STEPPER_CARRIAGES(self, gcmd): - toolhead = self.printer.lookup_object('toolhead') + toolhead = self.printer.lookup_object("toolhead") toolhead.flush_step_generation() stepper_name = gcmd.get("STEPPER") - steppers = [stepper for stepper in self.kin_steppers - if stepper.get_name() == stepper_name - or stepper.get_name(short=True) == stepper_name] + steppers = [ + stepper + for stepper in self.kin_steppers + if stepper.get_name() == stepper_name + or stepper.get_name(short=True) == stepper_name + ] if len(steppers) != 1: raise gcmd.error("Invalid STEPPER '%s' specified" % stepper_name) stepper = steppers[0] @@ -333,8 +409,10 @@ class GenericCartesianKinematics: new_carriages = stepper.get_carriages() if new_carriages != old_carriages: stepper.update_kin_coeffs(old_kin_coeffs) - raise gcmd.error("SET_STEPPER_CARRIAGES cannot add or remove " - "carriages that the stepper controls") + raise gcmd.error( + "SET_STEPPER_CARRIAGES cannot add or remove " + "carriages that the stepper controls" + ) pos = toolhead.get_position() stepper.set_position(pos) if not validate: @@ -346,13 +424,16 @@ class GenericCartesianKinematics: for acs in itertools.product(*zip(pcs, self.dc_carriages)): for c in acs: self.dc_module.get_dc_rail_wrapper(c.get_rail()).activate( - idex_modes.PRIMARY, pos) + idex_modes.PRIMARY, pos + ) self.dc_module.get_dc_rail_wrapper( - c.get_dual_carriage().get_rail()).inactivate(pos) + c.get_dual_carriage().get_rail() + ).inactivate(pos) self._check_kinematics(gcmd.error) self.dc_module.restore_dual_carriage_state(dc_state, move=0) else: self._check_kinematics(gcmd.error) + def load_kinematics(toolhead, config): return GenericCartesianKinematics(toolhead, config) diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index fd2621d5..2562ba33 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -7,61 +7,75 @@ import logging import stepper from . import idex_modes + # The hybrid-corexy kinematic is also known as Markforged kinematics class HybridCoreXYKinematics: def __init__(self, toolhead, config): self.printer = config.get_printer() # itersolve parameters - self.rails = [ stepper.LookupRail(config.getsection('stepper_x')), - stepper.LookupMultiRail(config.getsection('stepper_y')), - stepper.LookupMultiRail(config.getsection('stepper_z'))] - self.rails[1].get_endstops()[0][0].add_stepper( - self.rails[0].get_steppers()[0]) - self.rails[0].setup_itersolve('corexy_stepper_alloc', b'-') - self.rails[1].setup_itersolve('cartesian_stepper_alloc', b'y') - self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z') + self.rails = [ + stepper.LookupRail(config.getsection("stepper_x")), + stepper.LookupMultiRail(config.getsection("stepper_y")), + stepper.LookupMultiRail(config.getsection("stepper_z")), + ] + self.rails[1].get_endstops()[0][0].add_stepper(self.rails[0].get_steppers()[0]) + self.rails[0].setup_itersolve("corexy_stepper_alloc", b"-") + self.rails[1].setup_itersolve("cartesian_stepper_alloc", b"y") + self.rails[2].setup_itersolve("cartesian_stepper_alloc", b"z") ranges = [r.get_range() for r in self.rails] - self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.) - self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.) + self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0) + self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0) self.dc_module = None - if config.has_section('dual_carriage'): - dc_config = config.getsection('dual_carriage') + if config.has_section("dual_carriage"): + dc_config = config.getsection("dual_carriage") # dummy for cartesian config users - dc_config.getchoice('axis', ['x'], default='x') + dc_config.getchoice("axis", ["x"], default="x") # setup second dual carriage rail self.rails.append(stepper.LookupRail(dc_config)) self.rails[1].get_endstops()[0][0].add_stepper( - self.rails[3].get_steppers()[0]) - self.rails[3].setup_itersolve('corexy_stepper_alloc', b'+') + self.rails[3].get_steppers()[0] + ) + self.rails[3].setup_itersolve("corexy_stepper_alloc", b"+") self.dc_module = idex_modes.DualCarriages( - self.printer, [self.rails[0]], [self.rails[3]], axes=[0], - safe_dist=dc_config.getfloat( - 'safe_distance', None, minval=0.)) + self.printer, + [self.rails[0]], + [self.rails[3]], + axes=[0], + safe_dist=dc_config.getfloat("safe_distance", None, minval=0.0), + ) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( - 'max_z_velocity', max_velocity, above=0., maxval=max_velocity) + "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity + ) self.max_z_accel = config.getfloat( - 'max_z_accel', max_accel, above=0., maxval=max_accel) + "max_z_accel", max_accel, above=0.0, maxval=max_accel + ) self.limits = [(1.0, -1.0)] * 3 + def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] + def calc_position(self, stepper_positions): pos = [stepper_positions[rail.get_name()] for rail in self.rails] - if (self.dc_module is not None and 'PRIMARY' == \ - self.dc_module.get_status()['carriage_1']): + if ( + self.dc_module is not None + and "PRIMARY" == self.dc_module.get_status()["carriage_1"] + ): return [pos[3] - pos[1], pos[1], pos[2]] else: return [pos[0] + pos[1], pos[1], pos[2]] + def update_limits(self, i, range): l, h = self.limits[i] # Only update limits if this axis was already homed, # otherwise leave in un-homed state. if l <= h: self.limits[i] = range + def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) @@ -72,10 +86,12 @@ class HybridCoreXYKinematics: else: rail = self.rails[axis] self.limits[axis] = rail.get_range() + def clear_homing_state(self, clear_axes): for axis, axis_name in enumerate("xyz"): if axis_name in clear_axes: self.limits[axis] = (1.0, -1.0) + def home_axis(self, homing_state, axis, rail): position_min, position_max = rail.get_range() hi = rail.get_homing_info() @@ -88,26 +104,33 @@ class HybridCoreXYKinematics: forcepos[axis] += 1.5 * (position_max - hi.position_endstop) # Perform homing homing_state.home_rails([rail], forcepos, homepos) + def home(self, homing_state): for axis in homing_state.get_axes(): if self.dc_module is not None and axis == 0: self.dc_module.home(homing_state, axis) else: self.home_axis(homing_state, axis, self.rails[axis]) + def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): - if (move.axes_d[i] - and (end_pos[i] < self.limits[i][0] - or end_pos[i] > self.limits[i][1])): + if move.axes_d[i] and ( + end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1] + ): if self.limits[i][0] > self.limits[i][1]: raise move.move_error("Must home axis first") raise move.move_error() + def check_move(self, move): limits = self.limits xpos, ypos = move.end_pos[:2] - if (xpos < limits[0][0] or xpos > limits[0][1] - or ypos < limits[1][0] or ypos > limits[1][1]): + if ( + xpos < limits[0][0] + or xpos > limits[0][1] + or ypos < limits[1][0] + or ypos > limits[1][1] + ): self._check_endstops(move) if not move.axes_d[2]: # Normal XY move - use defaults @@ -115,15 +138,16 @@ class HybridCoreXYKinematics: # Move with Z - update velocity and accel for slower Z axis self._check_endstops(move) z_ratio = move.move_d / abs(move.axes_d[2]) - move.limit_speed( - self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + def get_status(self, eventtime): axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h] return { - 'homed_axes': "".join(axes), - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max + "homed_axes": "".join(axes), + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, } + def load_kinematics(toolhead, config): return HybridCoreXYKinematics(toolhead, config) diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index b9699982..6c3ae57d 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -7,61 +7,75 @@ import logging import stepper from . import idex_modes + # The hybrid-corexz kinematic is also known as Markforged kinematics class HybridCoreXZKinematics: def __init__(self, toolhead, config): self.printer = config.get_printer() # itersolve parameters - self.rails = [ stepper.LookupRail(config.getsection('stepper_x')), - stepper.LookupMultiRail(config.getsection('stepper_y')), - stepper.LookupMultiRail(config.getsection('stepper_z'))] - self.rails[2].get_endstops()[0][0].add_stepper( - self.rails[0].get_steppers()[0]) - self.rails[0].setup_itersolve('corexz_stepper_alloc', b'-') - self.rails[1].setup_itersolve('cartesian_stepper_alloc', b'y') - self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z') + self.rails = [ + stepper.LookupRail(config.getsection("stepper_x")), + stepper.LookupMultiRail(config.getsection("stepper_y")), + stepper.LookupMultiRail(config.getsection("stepper_z")), + ] + self.rails[2].get_endstops()[0][0].add_stepper(self.rails[0].get_steppers()[0]) + self.rails[0].setup_itersolve("corexz_stepper_alloc", b"-") + self.rails[1].setup_itersolve("cartesian_stepper_alloc", b"y") + self.rails[2].setup_itersolve("cartesian_stepper_alloc", b"z") ranges = [r.get_range() for r in self.rails] - self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.) - self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.) + self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0) + self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0) self.dc_module = None - if config.has_section('dual_carriage'): - dc_config = config.getsection('dual_carriage') + if config.has_section("dual_carriage"): + dc_config = config.getsection("dual_carriage") # dummy for cartesian config users - dc_config.getchoice('axis', ['x'], default='x') + dc_config.getchoice("axis", ["x"], default="x") # setup second dual carriage rail self.rails.append(stepper.LookupRail(dc_config)) self.rails[2].get_endstops()[0][0].add_stepper( - self.rails[3].get_steppers()[0]) - self.rails[3].setup_itersolve('corexz_stepper_alloc', b'+') + self.rails[3].get_steppers()[0] + ) + self.rails[3].setup_itersolve("corexz_stepper_alloc", b"+") self.dc_module = idex_modes.DualCarriages( - self.printer, [self.rails[0]], [self.rails[3]], axes=[0], - safe_dist=dc_config.getfloat( - 'safe_distance', None, minval=0.)) + self.printer, + [self.rails[0]], + [self.rails[3]], + axes=[0], + safe_dist=dc_config.getfloat("safe_distance", None, minval=0.0), + ) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( - 'max_z_velocity', max_velocity, above=0., maxval=max_velocity) + "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity + ) self.max_z_accel = config.getfloat( - 'max_z_accel', max_accel, above=0., maxval=max_accel) + "max_z_accel", max_accel, above=0.0, maxval=max_accel + ) self.limits = [(1.0, -1.0)] * 3 + def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] + def calc_position(self, stepper_positions): pos = [stepper_positions[rail.get_name()] for rail in self.rails] - if (self.dc_module is not None and 'PRIMARY' == \ - self.dc_module.get_status()['carriage_1']): + if ( + self.dc_module is not None + and "PRIMARY" == self.dc_module.get_status()["carriage_1"] + ): return [pos[3] - pos[2], pos[1], pos[2]] else: return [pos[0] + pos[2], pos[1], pos[2]] + def update_limits(self, i, range): l, h = self.limits[i] # Only update limits if this axis was already homed, # otherwise leave in un-homed state. if l <= h: self.limits[i] = range + def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) @@ -72,10 +86,12 @@ class HybridCoreXZKinematics: else: rail = self.rails[axis] self.limits[axis] = rail.get_range() + def clear_homing_state(self, clear_axes): for axis, axis_name in enumerate("xyz"): if axis_name in clear_axes: self.limits[axis] = (1.0, -1.0) + def home_axis(self, homing_state, axis, rail): position_min, position_max = rail.get_range() hi = rail.get_homing_info() @@ -88,26 +104,33 @@ class HybridCoreXZKinematics: forcepos[axis] += 1.5 * (position_max - hi.position_endstop) # Perform homing homing_state.home_rails([rail], forcepos, homepos) + def home(self, homing_state): for axis in homing_state.get_axes(): if self.dc_module is not None and axis == 0: self.dc_module.home(homing_state, axis) else: self.home_axis(homing_state, axis, self.rails[axis]) + def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): - if (move.axes_d[i] - and (end_pos[i] < self.limits[i][0] - or end_pos[i] > self.limits[i][1])): + if move.axes_d[i] and ( + end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1] + ): if self.limits[i][0] > self.limits[i][1]: raise move.move_error("Must home axis first") raise move.move_error() + def check_move(self, move): limits = self.limits xpos, ypos = move.end_pos[:2] - if (xpos < limits[0][0] or xpos > limits[0][1] - or ypos < limits[1][0] or ypos > limits[1][1]): + if ( + xpos < limits[0][0] + or xpos > limits[0][1] + or ypos < limits[1][0] + or ypos > limits[1][1] + ): self._check_endstops(move) if not move.axes_d[2]: # Normal XY move - use defaults @@ -115,15 +138,16 @@ class HybridCoreXZKinematics: # Move with Z - update velocity and accel for slower Z axis self._check_endstops(move) z_ratio = move.move_d / abs(move.axes_d[2]) - move.limit_speed( - self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + def get_status(self, eventtime): axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h] return { - 'homed_axes': "".join(axes), - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max + "homed_axes": "".join(axes), + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, } + def load_kinematics(toolhead, config): return HybridCoreXZKinematics(toolhead, config) diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py index 19d5a655..5910d5c0 100644 --- a/klippy/kinematics/idex_modes.py +++ b/klippy/kinematics/idex_modes.py @@ -7,29 +7,33 @@ import collections, logging, math import chelper -INACTIVE = 'INACTIVE' -PRIMARY = 'PRIMARY' -COPY = 'COPY' -MIRROR = 'MIRROR' +INACTIVE = "INACTIVE" +PRIMARY = "PRIMARY" +COPY = "COPY" +MIRROR = "MIRROR" + class DualCarriages: VALID_MODES = [PRIMARY, COPY, MIRROR] - def __init__(self, printer, primary_rails, dual_rails, axes, - safe_dist={}): + + def __init__(self, printer, primary_rails, dual_rails, axes, safe_dist={}): self.printer = printer self.axes = axes self._init_steppers(primary_rails + dual_rails) self.primary_rails = [ - DualCarriagesRail(printer, c, dual_rails[i], - axes[i], active=True) - for i, c in enumerate(primary_rails)] + DualCarriagesRail(printer, c, dual_rails[i], axes[i], active=True) + for i, c in enumerate(primary_rails) + ] self.dual_rails = [ - DualCarriagesRail(printer, c, primary_rails[i], - axes[i], active=False) - for i, c in enumerate(dual_rails)] + DualCarriagesRail(printer, c, primary_rails[i], axes[i], active=False) + for i, c in enumerate(dual_rails) + ] self.dc_rails = collections.OrderedDict( - [(c.rail.get_name(short=True), c) - for c in self.primary_rails + self.dual_rails]) + [ + (c.rail.get_name(short=True), c) + for c in self.primary_rails + self.dual_rails + ] + ) self.saved_states = {} self.safe_dist = {} for i, dc in enumerate(dual_rails): @@ -42,22 +46,29 @@ class DualCarriages: self.safe_dist[axis] = safe_dist continue pc = primary_rails[i] - self.safe_dist[axis] = min(abs(pc.position_min - dc.position_min), - abs(pc.position_max - dc.position_max)) - self.printer.add_object('dual_carriage', self) + self.safe_dist[axis] = min( + abs(pc.position_min - dc.position_min), + abs(pc.position_max - dc.position_max), + ) + self.printer.add_object("dual_carriage", self) self.printer.register_event_handler("klippy:ready", self._handle_ready) - gcode = self.printer.lookup_object('gcode') + gcode = self.printer.lookup_object("gcode") gcode.register_command( - 'SET_DUAL_CARRIAGE', self.cmd_SET_DUAL_CARRIAGE, - desc=self.cmd_SET_DUAL_CARRIAGE_help) + "SET_DUAL_CARRIAGE", + self.cmd_SET_DUAL_CARRIAGE, + desc=self.cmd_SET_DUAL_CARRIAGE_help, + ) gcode.register_command( - 'SAVE_DUAL_CARRIAGE_STATE', - self.cmd_SAVE_DUAL_CARRIAGE_STATE, - desc=self.cmd_SAVE_DUAL_CARRIAGE_STATE_help) + "SAVE_DUAL_CARRIAGE_STATE", + self.cmd_SAVE_DUAL_CARRIAGE_STATE, + desc=self.cmd_SAVE_DUAL_CARRIAGE_STATE_help, + ) gcode.register_command( - 'RESTORE_DUAL_CARRIAGE_STATE', - self.cmd_RESTORE_DUAL_CARRIAGE_STATE, - desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help) + "RESTORE_DUAL_CARRIAGE_STATE", + self.cmd_RESTORE_DUAL_CARRIAGE_STATE, + desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help, + ) + def _init_steppers(self, rails): ffi_main, ffi_lib = chelper.get_ffi() self.dc_stepper_kinematics = [] @@ -67,8 +78,9 @@ class DualCarriages: c_steppers = rail.get_steppers() if not c_steppers: raise self.printer.config_error( - "At least one stepper must be " - "associated with carriage: %s" % rail.get_name()) + "At least one stepper must be " + "associated with carriage: %s" % rail.get_name() + ) steppers.update(c_steppers) for s in steppers: sk = ffi_main.gc(ffi_lib.dual_carriage_alloc(), ffi_lib.free) @@ -77,28 +89,34 @@ class DualCarriages: self.dc_stepper_kinematics.append(sk) self.orig_stepper_kinematics.append(orig_sk) s.set_stepper_kinematics(sk) + def get_axes(self): return self.axes + def get_primary_rail(self, axis): for dc_rail in self.dc_rails.values(): if dc_rail.mode == PRIMARY and dc_rail.axis == axis: return dc_rail.rail return None + def get_dc_rail_wrapper(self, rail): for dc_rail in self.dc_rails.values(): if dc_rail.rail == rail: return dc_rail return None + def get_transform(self, rail): dc_rail = self.get_dc_rail_wrapper(rail) if dc_rail is not None: return (dc_rail.scale, dc_rail.offset) - return (0., 0.) + return (0.0, 0.0) + def is_active(self, rail): dc_rail = self.get_dc_rail_wrapper(rail) return dc_rail.is_active() if dc_rail is not None else False + def toggle_active_dc_rail(self, target_dc): - toolhead = self.printer.lookup_object('toolhead') + toolhead = self.printer.lookup_object("toolhead") toolhead.flush_step_generation() pos = toolhead.get_position() kin = toolhead.get_kinematics() @@ -107,16 +125,17 @@ class DualCarriages: if dc != target_dc and dc.axis == axis and dc.is_active(): dc.inactivate(pos) if target_dc.mode != PRIMARY: - newpos = pos[:axis] + [target_dc.get_axis_position(pos)] \ - + pos[axis+1:] + newpos = pos[:axis] + [target_dc.get_axis_position(pos)] + pos[axis + 1 :] target_dc.activate(PRIMARY, newpos, old_position=pos) toolhead.set_position(newpos) kin.update_limits(axis, target_dc.rail.get_range()) + def home(self, homing_state, axis): - kin = self.printer.lookup_object('toolhead').get_kinematics() + kin = self.printer.lookup_object("toolhead").get_kinematics() dcs = [dc for dc in self.dc_rails.values() if dc.axis == axis] - if (self.get_dc_order(dcs[0], dcs[1]) > 0) != \ - dcs[0].rail.get_homing_info().positive_dir: + if (self.get_dc_order(dcs[0], dcs[1]) > 0) != dcs[ + 0 + ].rail.get_homing_info().positive_dir: # The second carriage must home first, because the carriages home in # the same direction and the first carriage homes on the second one dcs.reverse() @@ -125,13 +144,20 @@ class DualCarriages: kin.home_axis(homing_state, axis, dc.rail) # Restore the original rails ordering self.toggle_active_dc_rail(dcs[0]) + def get_status(self, eventtime=None): - status = {'carriages' : {dc.get_name() : dc.mode - for dc in self.dc_rails.values()}} + status = { + "carriages": {dc.get_name(): dc.mode for dc in self.dc_rails.values()} + } if len(self.dc_rails) == 2: - status.update({('carriage_%d' % (i,)) : dc.mode - for i, dc in enumerate(self.dc_rails.values())}) + status.update( + { + ("carriage_%d" % (i,)): dc.mode + for i, dc in enumerate(self.dc_rails.values()) + } + ) return status + def get_kin_range(self, toolhead, mode, axis): pos = toolhead.get_position() dcs = [dc for dc in self.dc_rails.values() if dc.axis == axis] @@ -149,21 +175,19 @@ class DualCarriages: return (range_min, range_max) if mode == COPY: - range_min = max(range_min, - axes_pos[0] - axes_pos[1] + dc1_rail.position_min) - range_max = min(range_max, - axes_pos[0] - axes_pos[1] + dc1_rail.position_max) + range_min = max( + range_min, axes_pos[0] - axes_pos[1] + dc1_rail.position_min + ) + range_max = min( + range_max, axes_pos[0] - axes_pos[1] + dc1_rail.position_max + ) elif mode == MIRROR: if self.get_dc_order(dcs[0], dcs[1]) > 0: - range_min = max(range_min, - 0.5 * (sum(axes_pos) + safe_dist)) - range_max = min(range_max, - sum(axes_pos) - dc1_rail.position_min) + range_min = max(range_min, 0.5 * (sum(axes_pos) + safe_dist)) + range_max = min(range_max, sum(axes_pos) - dc1_rail.position_min) else: - range_max = min(range_max, - 0.5 * (sum(axes_pos) - safe_dist)) - range_min = max(range_min, - sum(axes_pos) - dc1_rail.position_max) + range_max = min(range_max, 0.5 * (sum(axes_pos) - safe_dist)) + range_min = max(range_min, sum(axes_pos) - dc1_rail.position_max) else: # mode == PRIMARY active_idx = 1 if dcs[1].is_active() else 0 @@ -184,6 +208,7 @@ class DualCarriages: # which actually permits carriage motion. return (range_min, range_min) return (range_min, range_max) + def get_dc_order(self, first_dc, second_dc): if first_dc == second_dc: return 0 @@ -201,8 +226,9 @@ class DualCarriages: if first_rail.position_endstop > second_rail.position_endstop: return 1 return -1 + def activate_dc_mode(self, dc, mode): - toolhead = self.printer.lookup_object('toolhead') + toolhead = self.printer.lookup_object("toolhead") toolhead.flush_step_generation() kin = toolhead.get_kinematics() axis = dc.axis @@ -214,14 +240,17 @@ class DualCarriages: self.toggle_active_dc_rail(self.get_dc_rail_wrapper(dc.dual_rail)) dc.activate(mode, toolhead.get_position()) kin.update_limits(axis, self.get_kin_range(toolhead, mode, axis)) + def _handle_ready(self): for dc_rail in self.dc_rails.values(): dc_rail.apply_transform() + cmd_SET_DUAL_CARRIAGE_help = "Configure the dual carriages mode" + def cmd_SET_DUAL_CARRIAGE(self, gcmd): - carriage_str = gcmd.get('CARRIAGE', None) + carriage_str = gcmd.get("CARRIAGE", None) if carriage_str is None: - raise gcmd.error('CARRIAGE must be specified') + raise gcmd.error("CARRIAGE must be specified") if carriage_str in self.dc_rails: dc_rail = self.dc_rails[carriage_str] else: @@ -230,65 +259,75 @@ class DualCarriages: try: index = int(carriage_str.strip()) if index < 0 or index > 1: - raise gcmd.error('Invalid CARRIAGE=%d index' % index) - dc_rail = (self.dual_rails if index - else self.primary_rails)[0] + raise gcmd.error("Invalid CARRIAGE=%d index" % index) + dc_rail = (self.dual_rails if index else self.primary_rails)[0] except ValueError: pass if dc_rail is None: - raise gcmd.error('Invalid CARRIAGE=%s specified' % carriage_str) - mode = gcmd.get('MODE', PRIMARY).upper() + raise gcmd.error("Invalid CARRIAGE=%s specified" % carriage_str) + mode = gcmd.get("MODE", PRIMARY).upper() if mode not in self.VALID_MODES: raise gcmd.error("Invalid mode=%s specified" % (mode,)) if mode in [COPY, MIRROR]: if dc_rail in self.primary_rails: raise gcmd.error( - "Mode=%s is not supported for carriage=%s" % ( - mode, dc_rail.get_name())) + "Mode=%s is not supported for carriage=%s" + % (mode, dc_rail.get_name()) + ) curtime = self.printer.get_reactor().monotonic() - kin = self.printer.lookup_object('toolhead').get_kinematics() - axis = 'xyz'[dc_rail.axis] - if axis not in kin.get_status(curtime)['homed_axes']: + kin = self.printer.lookup_object("toolhead").get_kinematics() + axis = "xyz"[dc_rail.axis] + if axis not in kin.get_status(curtime)["homed_axes"]: raise gcmd.error( - "Axis %s must be homed prior to enabling mode=%s" % - (axis.upper(), mode)) + "Axis %s must be homed prior to enabling mode=%s" + % (axis.upper(), mode) + ) self.activate_dc_mode(dc_rail, mode) - cmd_SAVE_DUAL_CARRIAGE_STATE_help = \ - "Save dual carriages modes and positions" + + cmd_SAVE_DUAL_CARRIAGE_STATE_help = "Save dual carriages modes and positions" + def cmd_SAVE_DUAL_CARRIAGE_STATE(self, gcmd): - state_name = gcmd.get('NAME', 'default') + state_name = gcmd.get("NAME", "default") self.saved_states[state_name] = self.save_dual_carriage_state() + def save_dual_carriage_state(self): - pos = self.printer.lookup_object('toolhead').get_position() - return {'carriage_modes': {dc.get_name() : dc.mode - for dc in self.dc_rails.values()}, - 'carriage_positions': {dc.get_name() : dc.get_axis_position(pos) - for dc in self.dc_rails.values()}} - cmd_RESTORE_DUAL_CARRIAGE_STATE_help = \ - "Restore dual carriages modes and positions" + pos = self.printer.lookup_object("toolhead").get_position() + return { + "carriage_modes": {dc.get_name(): dc.mode for dc in self.dc_rails.values()}, + "carriage_positions": { + dc.get_name(): dc.get_axis_position(pos) + for dc in self.dc_rails.values() + }, + } + + cmd_RESTORE_DUAL_CARRIAGE_STATE_help = "Restore dual carriages modes and positions" + def cmd_RESTORE_DUAL_CARRIAGE_STATE(self, gcmd): - state_name = gcmd.get('NAME', 'default') + state_name = gcmd.get("NAME", "default") saved_state = self.saved_states.get(state_name) if saved_state is None: raise gcmd.error("Unknown DUAL_CARRIAGE state: %s" % (state_name,)) - move_speed = gcmd.get_float('MOVE_SPEED', 0., above=0.) - move = gcmd.get_int('MOVE', 1) + move_speed = gcmd.get_float("MOVE_SPEED", 0.0, above=0.0) + move = gcmd.get_int("MOVE", 1) self.restore_dual_carriage_state(saved_state, move, move_speed) - def restore_dual_carriage_state(self, saved_state, move, move_speed=0.): - toolhead = self.printer.lookup_object('toolhead') + + def restore_dual_carriage_state(self, saved_state, move, move_speed=0.0): + toolhead = self.printer.lookup_object("toolhead") toolhead.flush_step_generation() if move: - homing_speed = 99999999. + homing_speed = 99999999.0 move_pos = list(toolhead.get_position()) cur_pos = [] - carriage_positions = saved_state['carriage_positions'] + carriage_positions = saved_state["carriage_positions"] dcs = list(self.dc_rails.values()) for dc in dcs: self.toggle_active_dc_rail(dc) homing_speed = min(homing_speed, dc.rail.homing_speed) cur_pos.append(toolhead.get_position()) - dl = [carriage_positions[dc.get_name()] - cur_pos[i][dc.axis] - for i, dc in enumerate(dcs)] + dl = [ + carriage_positions[dc.get_name()] - cur_pos[i][dc.axis] + for i, dc in enumerate(dcs) + ] for axis in self.axes: dc_ind = [i for i, dc in enumerate(dcs) if dc.axis == axis] if abs(dl[dc_ind[0]]) >= abs(dl[dc_ind[1]]): @@ -298,26 +337,29 @@ class DualCarriages: primary_dc = dcs[primary_ind] self.toggle_active_dc_rail(primary_dc) move_pos[axis] = carriage_positions[primary_dc.get_name()] - dc_mode = INACTIVE if min(abs(dl[primary_ind]), - abs(dl[secondary_ind])) < .000000001 \ - else COPY if dl[primary_ind] * dl[secondary_ind] > 0 \ - else MIRROR + dc_mode = ( + INACTIVE + if min(abs(dl[primary_ind]), abs(dl[secondary_ind])) < 0.000000001 + else COPY if dl[primary_ind] * dl[secondary_ind] > 0 else MIRROR + ) if dc_mode != INACTIVE: dcs[secondary_ind].activate(dc_mode, cur_pos[primary_ind]) dcs[secondary_ind].override_axis_scaling( - abs(dl[secondary_ind] / dl[primary_ind]), - cur_pos[primary_ind]) + abs(dl[secondary_ind] / dl[primary_ind]), cur_pos[primary_ind] + ) toolhead.manual_move(move_pos, move_speed or homing_speed) toolhead.flush_step_generation() # Make sure the scaling coefficients are restored with the mode for dc in dcs: dc.inactivate(move_pos) for dc in self.dc_rails.values(): - saved_mode = saved_state['carriage_modes'][dc.get_name()] + saved_mode = saved_state["carriage_modes"][dc.get_name()] self.activate_dc_mode(dc, saved_mode) + class DualCarriagesRail: - ENC_AXES = [b'x', b'y'] + ENC_AXES = [b"x", b"y"] + def __init__(self, printer, rail, dual_rail, axis, active): self.printer = printer self.rail = rail @@ -325,31 +367,39 @@ class DualCarriagesRail: self.sks = [s.get_stepper_kinematics() for s in rail.get_steppers()] self.axis = axis self.mode = (INACTIVE, PRIMARY)[active] - self.offset = 0. - self.scale = 1. if active else 0. + self.offset = 0.0 + self.scale = 1.0 if active else 0.0 + def get_name(self): return self.rail.get_name() + def is_active(self): return self.mode != INACTIVE + def get_axis_position(self, position): return position[self.axis] * self.scale + self.offset + def apply_transform(self): ffi_main, ffi_lib = chelper.get_ffi() for sk in self.sks: ffi_lib.dual_carriage_set_transform( - sk, self.ENC_AXES[self.axis], self.scale, self.offset) - self.printer.send_event('dual_carriage:update_kinematics') + sk, self.ENC_AXES[self.axis], self.scale, self.offset + ) + self.printer.send_event("dual_carriage:update_kinematics") + def activate(self, mode, position, old_position=None): old_axis_position = self.get_axis_position(old_position or position) - self.scale = -1. if mode == MIRROR else 1. + self.scale = -1.0 if mode == MIRROR else 1.0 self.offset = old_axis_position - position[self.axis] * self.scale self.apply_transform() self.mode = mode + def inactivate(self, position): self.offset = self.get_axis_position(position) - self.scale = 0. + self.scale = 0.0 self.apply_transform() self.mode = INACTIVE + def override_axis_scaling(self, new_scale, position): old_axis_position = self.get_axis_position(position) self.scale = math.copysign(new_scale, self.scale) diff --git a/klippy/kinematics/kinematic_stepper.py b/klippy/kinematics/kinematic_stepper.py index c82f0855..7d983ed1 100644 --- a/klippy/kinematics/kinematic_stepper.py +++ b/klippy/kinematics/kinematic_stepper.py @@ -7,86 +7,105 @@ import logging, re import stepper, chelper + def parse_carriages_string(carriages_str, printer_carriages, parse_error): nxt = 0 - pat = re.compile('[+-]') - coeffs = [0.] * 3 + pat = re.compile("[+-]") + coeffs = [0.0] * 3 ref_carriages = [] while nxt < len(carriages_str): - match = pat.search(carriages_str, nxt+1) + match = pat.search(carriages_str, nxt + 1) end = len(carriages_str) if match is None else match.start() term = carriages_str[nxt:end].strip() - term_lst = term.split('*') + term_lst = term.split("*") if len(term_lst) not in [1, 2]: - raise parse_error( - "Invalid term '%s' in '%s'" % (term, carriages_str)) + raise parse_error("Invalid term '%s' in '%s'" % (term, carriages_str)) if len(term_lst) == 2: try: coeff = float(term_lst[0]) except ValueError: raise error("Invalid float '%s'" % term_lst[0]) else: - coeff = -1. if term_lst[0].startswith('-') else 1. - if term_lst[0].startswith('-') or term_lst[0].startswith('+'): + coeff = -1.0 if term_lst[0].startswith("-") else 1.0 + if term_lst[0].startswith("-") or term_lst[0].startswith("+"): term_lst[0] = term_lst[0][1:] c = term_lst[-1] if c not in printer_carriages: - raise parse_error("Invalid '%s' carriage referenced in '%s'" % - (c, carriages_str)) + raise parse_error( + "Invalid '%s' carriage referenced in '%s'" % (c, carriages_str) + ) carriage = printer_carriages[c] j = carriage.get_axis() if coeffs[j]: - raise error("Carriage '%s' was referenced multiple times in '%s'" % - (c, carriages_str)) + raise error( + "Carriage '%s' was referenced multiple times in '%s'" + % (c, carriages_str) + ) coeffs[j] = coeff ref_carriages.append(carriage) nxt = end return coeffs, ref_carriages + class KinematicStepper: def __init__(self, config, printer_carriages): self.printer = config.get_printer() self.stepper = stepper.PrinterStepper(config) self.kin_coeffs, self.carriages = parse_carriages_string( - config.get('carriages'), printer_carriages, config.error) + config.get("carriages"), printer_carriages, config.error + ) if not any(self.kin_coeffs): raise config.error( - "'%s' must provide a valid 'carriages' configuration" % - self.stepper.get_name()) + "'%s' must provide a valid 'carriages' configuration" + % self.stepper.get_name() + ) self.stepper.setup_itersolve( - 'generic_cartesian_stepper_alloc', - self.kin_coeffs[0], self.kin_coeffs[1], self.kin_coeffs[2]) + "generic_cartesian_stepper_alloc", + self.kin_coeffs[0], + self.kin_coeffs[1], + self.kin_coeffs[2], + ) self.stepper_sk = self.stepper.get_stepper_kinematics() # Add stepper to the carriages it references for sc in self.carriages: sc.add_stepper(self) + def get_name(self, short=False): name = self.stepper.get_name(short) - if short and name.startswith('stepper '): + if short and name.startswith("stepper "): return name[8:] return name + def get_stepper(self): return self.stepper + def get_kin_coeffs(self): return tuple(self.kin_coeffs) + def get_active_axes(self): return [i for i, c in enumerate(self.kin_coeffs) if c] + def get_carriages(self): return self.carriages + def update_kin_coeffs(self, kin_coeffs): self.kin_coeffs = kin_coeffs ffi_main, ffi_lib = chelper.get_ffi() ffi_lib.generic_cartesian_stepper_set_coeffs( - self.stepper_sk, kin_coeffs[0], kin_coeffs[1], kin_coeffs[2]) + self.stepper_sk, kin_coeffs[0], kin_coeffs[1], kin_coeffs[2] + ) + def update_carriages(self, carriages_str, printer_carriages, report_error): kin_coeffs, carriages = parse_carriages_string( - carriages_str, printer_carriages, - report_error or self.printer.command_error) + carriages_str, printer_carriages, report_error or self.printer.command_error + ) if report_error is not None and not any(kin_coeffs): raise report_error( - "A valid string that references at least one carriage" - " must be provided for '%s'" % self.get_name()) + "A valid string that references at least one carriage" + " must be provided for '%s'" % self.get_name() + ) self.carriages = carriages self.update_kin_coeffs(kin_coeffs) + def set_position(self, coord): self.stepper.set_position(coord) diff --git a/klippy/kinematics/none.py b/klippy/kinematics/none.py index d9f9d21d..3c37217c 100644 --- a/klippy/kinematics/none.py +++ b/klippy/kinematics/none.py @@ -4,27 +4,36 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. + class NoneKinematics: def __init__(self, toolhead, config): - self.axes_minmax = toolhead.Coord(0., 0., 0., 0.) + self.axes_minmax = toolhead.Coord(0.0, 0.0, 0.0, 0.0) + def get_steppers(self): return [] + def calc_position(self, stepper_positions): return [0, 0, 0] + def set_position(self, newpos, homing_axes): pass + def clear_homing_state(self, clear_axes): pass + def home(self, homing_state): pass + def check_move(self, move): pass + def get_status(self, eventtime): return { - 'homed_axes': '', - 'axis_minimum': self.axes_minmax, - 'axis_maximum': self.axes_minmax, + "homed_axes": "", + "axis_minimum": self.axes_minmax, + "axis_maximum": self.axes_minmax, } + def load_kinematics(toolhead, config): return NoneKinematics(toolhead, config) diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py index ffa15d83..cd480a42 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -6,55 +6,64 @@ import logging, math import stepper + class PolarKinematics: def __init__(self, toolhead, config): # Setup axis steppers - stepper_bed = stepper.PrinterStepper(config.getsection('stepper_bed'), - units_in_radians=True) - rail_arm = stepper.LookupRail(config.getsection('stepper_arm')) - rail_z = stepper.LookupMultiRail(config.getsection('stepper_z')) - stepper_bed.setup_itersolve('polar_stepper_alloc', b'a') - rail_arm.setup_itersolve('polar_stepper_alloc', b'r') - rail_z.setup_itersolve('cartesian_stepper_alloc', b'z') + stepper_bed = stepper.PrinterStepper( + config.getsection("stepper_bed"), units_in_radians=True + ) + rail_arm = stepper.LookupRail(config.getsection("stepper_arm")) + rail_z = stepper.LookupMultiRail(config.getsection("stepper_z")) + stepper_bed.setup_itersolve("polar_stepper_alloc", b"a") + rail_arm.setup_itersolve("polar_stepper_alloc", b"r") + rail_z.setup_itersolve("cartesian_stepper_alloc", b"z") self.rails = [rail_arm, rail_z] - self.steppers = [stepper_bed] + [ s for r in self.rails - for s in r.get_steppers() ] + self.steppers = [stepper_bed] + [ + s for r in self.rails for s in r.get_steppers() + ] for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( - 'max_z_velocity', max_velocity, above=0., maxval=max_velocity) + "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity + ) self.max_z_accel = config.getfloat( - 'max_z_accel', max_accel, above=0., maxval=max_accel) + "max_z_accel", max_accel, above=0.0, maxval=max_accel + ) self.limit_z = (1.0, -1.0) - self.limit_xy2 = -1. + self.limit_xy2 = -1.0 max_xy = self.rails[0].get_range()[1] min_z, max_z = self.rails[1].get_range() - self.axes_min = toolhead.Coord(-max_xy, -max_xy, min_z, 0.) - self.axes_max = toolhead.Coord(max_xy, max_xy, max_z, 0.) + self.axes_min = toolhead.Coord(-max_xy, -max_xy, min_z, 0.0) + self.axes_max = toolhead.Coord(max_xy, max_xy, max_z, 0.0) + def get_steppers(self): return list(self.steppers) + def calc_position(self, stepper_positions): bed_angle = stepper_positions[self.steppers[0].get_name()] arm_pos = stepper_positions[self.rails[0].get_name()] z_pos = stepper_positions[self.rails[1].get_name()] - return [math.cos(bed_angle) * arm_pos, math.sin(bed_angle) * arm_pos, - z_pos] + return [math.cos(bed_angle) * arm_pos, math.sin(bed_angle) * arm_pos, z_pos] + def set_position(self, newpos, homing_axes): for s in self.steppers: s.set_position(newpos) if "z" in homing_axes: self.limit_z = self.rails[1].get_range() if "x" in homing_axes and "y" in homing_axes: - self.limit_xy2 = self.rails[0].get_range()[1]**2 + self.limit_xy2 = self.rails[0].get_range()[1] ** 2 + def clear_homing_state(self, clear_axes): if "x" in clear_axes or "y" in clear_axes: # X and Y cannot be cleared separately - self.limit_xy2 = -1. + self.limit_xy2 = -1.0 if "z" in clear_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() @@ -62,7 +71,7 @@ class PolarKinematics: homepos = [None, None, None, None] homepos[axis] = hi.position_endstop if axis == 0: - homepos[1] = 0. + homepos[1] = 0.0 forcepos = list(homepos) if hi.positive_dir: forcepos[axis] -= hi.position_endstop - position_min @@ -70,6 +79,7 @@ class PolarKinematics: forcepos[axis] += position_max - hi.position_endstop # Perform homing homing_state.home_rails([rail], forcepos, homepos) + def home(self, homing_state): # Always home XY together homing_axes = homing_state.get_axes() @@ -86,11 +96,12 @@ class PolarKinematics: self._home_axis(homing_state, 0, self.rails[0]) if home_z: self._home_axis(homing_state, 2, self.rails[1]) + def check_move(self, move): end_pos = move.end_pos - xy2 = end_pos[0]**2 + end_pos[1]**2 + xy2 = end_pos[0] ** 2 + end_pos[1] ** 2 if xy2 > self.limit_xy2: - if self.limit_xy2 < 0.: + if self.limit_xy2 < 0.0: raise move.move_error("Must home axis first") raise move.move_error() if move.axes_d[2]: @@ -100,16 +111,17 @@ class PolarKinematics: raise move.move_error() # Move with Z - update velocity and accel for slower Z axis z_ratio = move.move_d / abs(move.axes_d[2]) - move.limit_speed(self.max_z_velocity * z_ratio, - self.max_z_accel * z_ratio) + move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio) + def get_status(self, eventtime): - xy_home = "xy" if self.limit_xy2 >= 0. else "" + xy_home = "xy" if self.limit_xy2 >= 0.0 else "" z_home = "z" if self.limit_z[0] <= self.limit_z[1] else "" return { - 'homed_axes': xy_home + z_home, - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max, + "homed_axes": xy_home + z_home, + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, } + def load_kinematics(toolhead, config): return PolarKinematics(toolhead, config) diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py index 732a89a8..7bc3a4d8 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -6,102 +6,130 @@ import math, logging import stepper, mathutil, chelper + class RotaryDeltaKinematics: def __init__(self, toolhead, config): # Setup tower rails - stepper_configs = [config.getsection('stepper_' + a) for a in 'abc'] + stepper_configs = [config.getsection("stepper_" + a) for a in "abc"] rail_a = stepper.LookupRail( - stepper_configs[0], need_position_minmax=False, - units_in_radians=True) + stepper_configs[0], need_position_minmax=False, units_in_radians=True + ) a_endstop = rail_a.get_homing_info().position_endstop rail_b = stepper.LookupRail( - stepper_configs[1], need_position_minmax=False, - default_position_endstop=a_endstop, units_in_radians=True) + stepper_configs[1], + need_position_minmax=False, + default_position_endstop=a_endstop, + units_in_radians=True, + ) rail_c = stepper.LookupRail( - stepper_configs[2], need_position_minmax=False, - default_position_endstop=a_endstop, units_in_radians=True) + stepper_configs[2], + need_position_minmax=False, + default_position_endstop=a_endstop, + units_in_radians=True, + ) self.rails = [rail_a, rail_b, rail_c] # Read config max_velocity, max_accel = toolhead.get_max_velocity() - self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, - above=0., maxval=max_velocity) - shoulder_radius = config.getfloat('shoulder_radius', above=0.) - shoulder_height = config.getfloat('shoulder_height', above=0.) - a_upper_arm = stepper_configs[0].getfloat('upper_arm_length', above=0.) + self.max_z_velocity = config.getfloat( + "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity + ) + shoulder_radius = config.getfloat("shoulder_radius", above=0.0) + shoulder_height = config.getfloat("shoulder_height", above=0.0) + a_upper_arm = stepper_configs[0].getfloat("upper_arm_length", above=0.0) upper_arms = [ - sconfig.getfloat('upper_arm_length', a_upper_arm, above=0.) - for sconfig in stepper_configs] - a_lower_arm = stepper_configs[0].getfloat('lower_arm_length', above=0.) + sconfig.getfloat("upper_arm_length", a_upper_arm, above=0.0) + for sconfig in stepper_configs + ] + a_lower_arm = stepper_configs[0].getfloat("lower_arm_length", above=0.0) lower_arms = [ - sconfig.getfloat('lower_arm_length', a_lower_arm, above=0.) - for sconfig in stepper_configs] - angles = [sconfig.getfloat('angle', angle) - for sconfig, angle in zip(stepper_configs, [30., 150., 270.])] + sconfig.getfloat("lower_arm_length", a_lower_arm, above=0.0) + for sconfig in stepper_configs + ] + angles = [ + sconfig.getfloat("angle", angle) + for sconfig, angle in zip(stepper_configs, [30.0, 150.0, 270.0]) + ] # Setup rotary delta calibration helper - endstops = [rail.get_homing_info().position_endstop - for rail in self.rails] - stepdists = [rail.get_steppers()[0].get_step_dist() - for rail in self.rails] + endstops = [rail.get_homing_info().position_endstop for rail in self.rails] + stepdists = [rail.get_steppers()[0].get_step_dist() for rail in self.rails] self.calibration = RotaryDeltaCalibration( - shoulder_radius, shoulder_height, angles, upper_arms, lower_arms, - endstops, stepdists) + shoulder_radius, + shoulder_height, + angles, + upper_arms, + lower_arms, + endstops, + stepdists, + ) # Setup iterative solver for r, a, ua, la in zip(self.rails, angles, upper_arms, lower_arms): - r.setup_itersolve('rotary_delta_stepper_alloc', - shoulder_radius, shoulder_height, - math.radians(a), ua, la) + r.setup_itersolve( + "rotary_delta_stepper_alloc", + shoulder_radius, + shoulder_height, + math.radians(a), + ua, + la, + ) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) # Setup boundary checks self.need_home = True - self.limit_xy2 = -1. - eangles = [r.calc_position_from_coord([0., 0., ep]) - for r, ep in zip(self.rails, endstops)] - self.home_position = tuple( - self.calibration.actuator_to_cartesian(eangles)) + self.limit_xy2 = -1.0 + eangles = [ + r.calc_position_from_coord([0.0, 0.0, ep]) + for r, ep in zip(self.rails, endstops) + ] + self.home_position = tuple(self.calibration.actuator_to_cartesian(eangles)) self.max_z = min(endstops) - self.min_z = config.getfloat('minimum_z_position', 0, maxval=self.max_z) + self.min_z = config.getfloat("minimum_z_position", 0, maxval=self.max_z) min_ua = min([shoulder_radius + ua for ua in upper_arms]) min_la = min([la - shoulder_radius for la in lower_arms]) - self.max_xy2 = min(min_ua, min_la)**2 - arm_z = [self.calibration.elbow_coord(i, ea)[2] - for i, ea in enumerate(eangles)] + self.max_xy2 = min(min_ua, min_la) ** 2 + arm_z = [self.calibration.elbow_coord(i, ea)[2] for i, ea in enumerate(eangles)] self.limit_z = min([az - la for az, la in zip(arm_z, lower_arms)]) logging.info( "Delta max build height %.2fmm (radius tapered above %.2fmm)" - % (self.max_z, self.limit_z)) + % (self.max_z, self.limit_z) + ) max_xy = math.sqrt(self.max_xy2) - self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.) - self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.) - self.set_position([0., 0., 0.], "") + self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.0) + self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.0) + self.set_position([0.0, 0.0, 0.0], "") + def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] + def calc_position(self, stepper_positions): spos = [stepper_positions[rail.get_name()] for rail in self.rails] return self.calibration.actuator_to_cartesian(spos) + def set_position(self, newpos, homing_axes): for rail in self.rails: rail.set_position(newpos) - self.limit_xy2 = -1. + self.limit_xy2 = -1.0 if homing_axes == "xyz": self.need_home = False + def clear_homing_state(self, clear_axes): # Clearing homing state for each axis individually is not implemented if clear_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) - #min_angles = [-.5 * math.pi] * 3 - #forcepos[2] = self.calibration.actuator_to_cartesian(min_angles)[2] - forcepos[2] = -1. + # min_angles = [-.5 * math.pi] * 3 + # forcepos[2] = self.calibration.actuator_to_cartesian(min_angles)[2] + forcepos[2] = -1.0 homing_state.home_rails(self.rails, forcepos, self.home_position) + def check_move(self, move): end_pos = move.end_pos - end_xy2 = end_pos[0]**2 + end_pos[1]**2 + end_xy2 = end_pos[0] ** 2 + end_pos[1] ** 2 if end_xy2 <= self.limit_xy2 and not move.axes_d[2]: # Normal XY move return @@ -110,30 +138,44 @@ class RotaryDeltaKinematics: end_z = end_pos[2] limit_xy2 = self.max_xy2 if end_z > self.limit_z: - limit_xy2 = min(limit_xy2, (self.max_z - end_z)**2) + limit_xy2 = min(limit_xy2, (self.max_z - end_z) ** 2) if end_xy2 > limit_xy2 or end_z > self.max_z or end_z < self.min_z: # Move out of range - verify not a homing move - if (end_pos[:2] != self.home_position[:2] - or end_z < self.min_z or end_z > self.home_position[2]): + if ( + end_pos[:2] != self.home_position[:2] + or end_z < self.min_z + or end_z > self.home_position[2] + ): raise move.move_error() - limit_xy2 = -1. + limit_xy2 = -1.0 if move.axes_d[2]: move.limit_speed(self.max_z_velocity, move.accel) - limit_xy2 = -1. + limit_xy2 = -1.0 self.limit_xy2 = limit_xy2 + def get_status(self, eventtime): return { - 'homed_axes': '' if self.need_home else 'xyz', - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max, + "homed_axes": "" if self.need_home else "xyz", + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, } + def get_calibration(self): return self.calibration + # Rotary delta parameter calibration for DELTA_CALIBRATE tool class RotaryDeltaCalibration: - def __init__(self, shoulder_radius, shoulder_height, angles, - upper_arms, lower_arms, endstops, stepdists): + def __init__( + self, + shoulder_radius, + shoulder_height, + angles, + upper_arms, + lower_arms, + endstops, + stepdists, + ): self.shoulder_radius = shoulder_radius self.shoulder_height = shoulder_height self.angles = angles @@ -143,39 +185,56 @@ class RotaryDeltaCalibration: self.stepdists = stepdists # Calculate the absolute angle of each endstop ffi_main, self.ffi_lib = chelper.get_ffi() - self.sks = [ffi_main.gc(self.ffi_lib.rotary_delta_stepper_alloc( - shoulder_radius, shoulder_height, math.radians(a), ua, la), - self.ffi_lib.free) - for a, ua, la in zip(angles, upper_arms, lower_arms)] + self.sks = [ + ffi_main.gc( + self.ffi_lib.rotary_delta_stepper_alloc( + shoulder_radius, shoulder_height, math.radians(a), ua, la + ), + self.ffi_lib.free, + ) + for a, ua, la in zip(angles, upper_arms, lower_arms) + ] self.abs_endstops = [ - self.ffi_lib.itersolve_calc_position_from_coord(sk, 0., 0., es) - for sk, es in zip(self.sks, endstops)] + self.ffi_lib.itersolve_calc_position_from_coord(sk, 0.0, 0.0, es) + for sk, es in zip(self.sks, endstops) + ] + def coordinate_descent_params(self, is_extended): # Determine adjustment parameters (for use with coordinate_descent) - adj_params = ('shoulder_height', 'endstop_a', 'endstop_b', 'endstop_c') + adj_params = ("shoulder_height", "endstop_a", "endstop_b", "endstop_c") if is_extended: - adj_params += ('shoulder_radius', 'angle_a', 'angle_b') - params = { 'shoulder_radius': self.shoulder_radius, - 'shoulder_height': self.shoulder_height } - for i, axis in enumerate('abc'): - params['angle_'+axis] = self.angles[i] - params['upper_arm_'+axis] = self.upper_arms[i] - params['lower_arm_'+axis] = self.lower_arms[i] - params['endstop_'+axis] = self.endstops[i] - params['stepdist_'+axis] = self.stepdists[i] + adj_params += ("shoulder_radius", "angle_a", "angle_b") + params = { + "shoulder_radius": self.shoulder_radius, + "shoulder_height": self.shoulder_height, + } + for i, axis in enumerate("abc"): + params["angle_" + axis] = self.angles[i] + params["upper_arm_" + axis] = self.upper_arms[i] + params["lower_arm_" + axis] = self.lower_arms[i] + params["endstop_" + axis] = self.endstops[i] + params["stepdist_" + axis] = self.stepdists[i] return adj_params, params + def new_calibration(self, params): # Create a new calibration object from coordinate_descent params - shoulder_radius = params['shoulder_radius'] - shoulder_height = params['shoulder_height'] - angles = [params['angle_'+a] for a in 'abc'] - upper_arms = [params['upper_arm_'+a] for a in 'abc'] - lower_arms = [params['lower_arm_'+a] for a in 'abc'] - endstops = [params['endstop_'+a] for a in 'abc'] - stepdists = [params['stepdist_'+a] for a in 'abc'] + shoulder_radius = params["shoulder_radius"] + shoulder_height = params["shoulder_height"] + angles = [params["angle_" + a] for a in "abc"] + upper_arms = [params["upper_arm_" + a] for a in "abc"] + lower_arms = [params["lower_arm_" + a] for a in "abc"] + endstops = [params["endstop_" + a] for a in "abc"] + stepdists = [params["stepdist_" + a] for a in "abc"] return RotaryDeltaCalibration( - shoulder_radius, shoulder_height, angles, upper_arms, lower_arms, - endstops, stepdists) + shoulder_radius, + shoulder_height, + angles, + upper_arms, + lower_arms, + endstops, + stepdists, + ) + def elbow_coord(self, elbow_id, spos): # Calculate elbow position in coordinate system at shoulder joint sj_elbow_x = self.upper_arms[elbow_id] * math.cos(spos) @@ -186,43 +245,59 @@ class RotaryDeltaCalibration: y = (sj_elbow_x + self.shoulder_radius) * math.sin(angle) z = sj_elbow_y + self.shoulder_height return (x, y, z) + def actuator_to_cartesian(self, spos): sphere_coords = [self.elbow_coord(i, sp) for i, sp in enumerate(spos)] lower_arm2 = [la**2 for la in self.lower_arms] return mathutil.trilateration(sphere_coords, lower_arm2) + def get_position_from_stable(self, stable_position): # Return cartesian coordinates for the given stable_position - spos = [ea - sp * sd - for ea, sp, sd in zip(self.abs_endstops, stable_position, - self.stepdists)] + spos = [ + ea - sp * sd + for ea, sp, sd in zip(self.abs_endstops, stable_position, self.stepdists) + ] return self.actuator_to_cartesian(spos) + def calc_stable_position(self, coord): # Return a stable_position from a cartesian coordinate - pos = [ self.ffi_lib.itersolve_calc_position_from_coord( - sk, coord[0], coord[1], coord[2]) - for sk in self.sks ] - return [(ep - sp) / sd - for sd, ep, sp in zip(self.stepdists, self.abs_endstops, pos)] + pos = [ + self.ffi_lib.itersolve_calc_position_from_coord( + sk, coord[0], coord[1], coord[2] + ) + for sk in self.sks + ] + return [ + (ep - sp) / sd for sd, ep, sp in zip(self.stepdists, self.abs_endstops, pos) + ] + def save_state(self, configfile): # Save the current parameters (for use with SAVE_CONFIG) - configfile.set('printer', 'shoulder_radius', "%.6f" - % (self.shoulder_radius,)) - configfile.set('printer', 'shoulder_height', "%.6f" - % (self.shoulder_height,)) - for i, axis in enumerate('abc'): - configfile.set('stepper_'+axis, 'angle', "%.6f" % (self.angles[i],)) - configfile.set('stepper_'+axis, 'position_endstop', - "%.6f" % (self.endstops[i],)) + configfile.set("printer", "shoulder_radius", "%.6f" % (self.shoulder_radius,)) + configfile.set("printer", "shoulder_height", "%.6f" % (self.shoulder_height,)) + for i, axis in enumerate("abc"): + configfile.set("stepper_" + axis, "angle", "%.6f" % (self.angles[i],)) + configfile.set( + "stepper_" + axis, "position_endstop", "%.6f" % (self.endstops[i],) + ) gcode = configfile.get_printer().lookup_object("gcode") gcode.respond_info( "stepper_a: position_endstop: %.6f angle: %.6f\n" "stepper_b: position_endstop: %.6f angle: %.6f\n" "stepper_c: position_endstop: %.6f angle: %.6f\n" "shoulder_radius: %.6f shoulder_height: %.6f" - % (self.endstops[0], self.angles[0], - self.endstops[1], self.angles[1], - self.endstops[2], self.angles[2], - self.shoulder_radius, self.shoulder_height)) + % ( + self.endstops[0], + self.angles[0], + self.endstops[1], + self.angles[1], + self.endstops[2], + self.angles[2], + self.shoulder_radius, + self.shoulder_height, + ) + ) + def load_kinematics(toolhead, config): return RotaryDeltaKinematics(toolhead, config) diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py index 47bc6855..290a7f27 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -5,54 +5,63 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import stepper, mathutil + class WinchKinematics: def __init__(self, toolhead, config): # Setup steppers at each anchor self.steppers = [] self.anchors = [] for i in range(26): - name = 'stepper_' + chr(ord('a') + i) + name = "stepper_" + chr(ord("a") + i) if i >= 3 and not config.has_section(name): break stepper_config = config.getsection(name) s = stepper.PrinterStepper(stepper_config) self.steppers.append(s) - a = tuple([stepper_config.getfloat('anchor_' + n) for n in 'xyz']) + a = tuple([stepper_config.getfloat("anchor_" + n) for n in "xyz"]) self.anchors.append(a) - s.setup_itersolve('winch_stepper_alloc', *a) + s.setup_itersolve("winch_stepper_alloc", *a) s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) # Setup boundary checks acoords = list(zip(*self.anchors)) - self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.) - self.axes_max = toolhead.Coord(*[max(a) for a in acoords], e=0.) - self.set_position([0., 0., 0.], "") + self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.0) + self.axes_max = toolhead.Coord(*[max(a) for a in acoords], e=0.0) + self.set_position([0.0, 0.0, 0.0], "") + def get_steppers(self): return list(self.steppers) + def calc_position(self, stepper_positions): # Use only first three steppers to calculate cartesian position pos = [stepper_positions[rail.get_name()] for rail in self.steppers[:3]] - return mathutil.trilateration(self.anchors[:3], [sp*sp for sp in pos]) + return mathutil.trilateration(self.anchors[:3], [sp * sp for sp in pos]) + def set_position(self, newpos, homing_axes): for s in self.steppers: s.set_position(newpos) + def clear_homing_state(self, clear_axes): # XXX - homing not implemented pass + def home(self, homing_state): # XXX - homing not implemented homing_state.set_axes([0, 1, 2]) - homing_state.set_homed_position([0., 0., 0.]) + homing_state.set_homed_position([0.0, 0.0, 0.0]) + def check_move(self, move): # XXX - boundary checks and speed limits not implemented pass + def get_status(self, eventtime): # XXX - homed_checks and rail limits not implemented return { - 'homed_axes': 'xyz', - 'axis_minimum': self.axes_min, - 'axis_maximum': self.axes_max, + "homed_axes": "xyz", + "axis_minimum": self.axes_min, + "axis_maximum": self.axes_max, } + def load_kinematics(toolhead, config): return WinchKinematics(toolhead, config) |