diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 8e7ed287a..8b17d9502 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -743,7 +743,7 @@ max_accel: ``` -Then a user must define three carriages for X, Y, and Z axes, e.g.: +Then a user must define three primary carriages for X, Y, and Z axes, e.g.: ``` [carriage carriage_x] axis: @@ -2455,10 +2455,16 @@ Please note that in this case the `[dual_carriage]` configuration deviates from the configuration described above: ``` [dual_carriage my_dc_carriage] -primary_carriage: -# Defines the matching primary carriage of this dual carriage and -# the corresponding IDEX axis. Must match a name of a defined `[carriage]`. -# This parameter must be provided. +#primary_carriage: +# Defines the matching carriage on the same gantry as this dual carriage and +# the corresponding dual axis. Must match a name of a defined `[carriage]` or +# another independent `[dual_carriage]`. If not set, which is a default, +# defines a dual carriage independent of a `[carriage]` with the same axis +# as this one (e.g. on a different gantry). +#axis: +# Axis of a carriage, either x or y. If 'primary_carriage' is defined, then +# this parameter defaults to the 'axis' parameter of that primary carriage, +# otherwise this parameter must be defined. #safe_distance: # The minimum distance (in mm) to enforce between the dual and the primary # carriages. If a G-Code command is executed that will bring the carriages @@ -2467,7 +2473,8 @@ primary_carriage: # position_min and position_max for the dual and primary carriages. If set # to 0 (or safe_distance is unset and position_min and position_max are # identical for the primary and dual carriages), the carriages proximity -# checks will be disabled. +# checks will be disabled. Only valid for a dual_carriage with a defined +# 'primary_carriage'. endstop_pin: #position_min: position_endstop: diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 6869cbc62..915537411 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -350,8 +350,9 @@ reference a defined primary or dual carriage for `generic_cartesian` kinematics or be 0 (for primary carriage) or 1 (for dual carriage) for all other kinematics supporting IDEX. Setting the mode to `PRIMARY` deactivates the other carriage and makes the specified carriage execute -subsequent G-Code commands as-is. `COPY` and `MIRROR` modes are supported -only for dual carriages. When set to either of these modes, dual carriage +subsequent G-Code commands as-is. Before activating `COPY` or `MIRROR` +mode for a carriage, a different one must be activated as `PRIMARY` on +the same axis. When set to either of these two modes, the carriage will then track the subsequent moves of its primary carriage and either copy relative movements of it (in `COPY` mode) or execute them in the opposite (mirror) direction (in `MIRROR` mode). diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 5362e57d0..67858d0bb 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -32,8 +32,8 @@ class CartKinematics: 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.)) + safe_dist=[dc_config.getfloat( + 'safe_distance', None, minval=0.)]) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) # Setup boundary checks diff --git a/klippy/kinematics/generic_cartesian.py b/klippy/kinematics/generic_cartesian.py index 230997be3..afb5d47ac 100644 --- a/klippy/kinematics/generic_cartesian.py +++ b/klippy/kinematics/generic_cartesian.py @@ -4,11 +4,13 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. -import copy, itertools, logging, math +import collections, copy, itertools, logging, math import gcode, mathutil, stepper from . import idex_modes from . import kinematic_stepper as ks +VALID_AXES = ['x', 'y', 'z'] + def mat_mul(a, b): if len(a[0]) != len(b): return None @@ -35,13 +37,11 @@ class MainCarriage: def __init__(self, config): self.rail = stepper.GenericPrinterRail(config) carriage_name = self.rail.get_name(short=True) - valid_axes = ['x', 'y', 'z'] - if carriage_name in valid_axes: - axis_name = config.getchoice('axis', valid_axes, carriage_name) + if carriage_name in VALID_AXES: + self.axis_name = config.getchoice('axis', VALID_AXES, carriage_name) else: - axis_name = config.getchoice('axis', valid_axes) - self.axis = ord(axis_name) - ord('x') - self.axis_name = axis_name + self.axis_name = config.getchoice('axis', VALID_AXES) + self.axis = ord(self.axis_name) - ord('x') self.dual_carriage = None def get_name(self): return self.rail.get_name(short=True) @@ -56,9 +56,7 @@ class MainCarriage: 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 @@ -78,23 +76,49 @@ class ExtraCarriage: self.endstop_pin, self.name) class DualCarriage: - def __init__(self, config, carriages): + def __init__(self, config): self.printer = config.get_printer() self.rail = stepper.GenericPrinterRail(config) - 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()) + self.primary_carriage_name = config.get('primary_carriage', None) + if self.primary_carriage_name is None: + self.axis_name = config.getchoice('axis', VALID_AXES) + self.axis = ord(self.axis_name) - ord('x') + self.safe_dist = None + else: + self.axis_name = config.getchoice('axis', VALID_AXES + [None], None) + self.safe_dist = config.getfloat('safe_distance', None, minval=0.) + self.primary_carriage = self.dual_carriage = None + self.config_error = config.error + def resolve_primary_carriage(self, carriages): + if self.primary_carriage_name is None: + return + if self.primary_carriage_name not in carriages: + raise self.config_error( + "primary_carriage = '%s' for '%s' is not a valid choice" + % (self.primary_carriage_name, self.get_name())) + self.primary_carriage = carriages[self.primary_carriage_name] + axis_name = self.axis_name or self.primary_carriage.axis_name + if axis_name != self.primary_carriage.axis_name: + raise self.config_error("Mismatching axes between carriage '%s' " + "(axis=%s) and dual_carriage '%s' (axis=%s)" + % (self.primary_carriage.get_name(), + self.primary_carriage.axis_name, + self.get_name(), axis_name)) + self.axis = ord(axis_name) - ord('x') + if self.primary_carriage.get_dual_carriage(): + raise self.config_error( + "Multiple dual carriages ('%s', '%s') for carriage '%s'" % + (self.primary_carriage.get_dual_carriage().get_name(), + self.get_name(), self.primary_carriage.get_name())) + self.primary_carriage.set_dual_carriage(self) self.axis = self.primary_carriage.get_axis() if self.axis > 1: - raise config.error("Invalid axis '%s' for dual_carriage" % - "xyz"[self.axis]) - self.safe_dist = config.getfloat('safe_distance', None, minval=0.) + raise self.config_error("Invalid axis '%s' for dual_carriage '%s'" % + ("xyz"[self.axis], self.get_name())) def get_name(self): return self.rail.get_name(short=True) def get_axis(self): - return self.primary_carriage.get_axis() + return self.axis def get_rail(self): return self.rail def get_safe_dist(self): @@ -103,7 +127,13 @@ class DualCarriage: return self.printer.lookup_object('dual_carriage') def is_active(self): return self.get_dc_module().is_active(self.rail) + def set_dual_carriage(self, carriage): + self.dual_carriage = carriage def get_dual_carriage(self): + if self.dual_carriage is not None: + return self.dual_carriage + return self.primary_carriage + def get_primary_carriage(self): return self.primary_carriage def add_stepper(self, kin_stepper): self.rail.add_stepper(kin_stepper.get_stepper()) @@ -116,27 +146,38 @@ class GenericCartesianKinematics: s.set_trapq(toolhead.get_trapq()) self.dc_module = None if self.dc_carriages: - pcs = [dc.get_dual_carriage() for dc in self.dc_carriages] + dc_axes = set(dc.get_axis() for dc in self.dc_carriages) + pcs = ([pc for pc in self.primary_carriages + if pc.get_axis() in dc_axes] + + [dc for dc in self.dc_carriages + if dc.get_primary_carriage() is None]) + dcs = [pc.get_dual_carriage() for pc in pcs] 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} + dual_rails = [dc.get_rail() if dc else None for dc in dcs] + axes = [pc.get_axis() for pc in pcs] + safe_dist = [dc.get_safe_dist() if dc else None for dc in dcs] self.dc_module = dc_module = idex_modes.DualCarriages( self.printer, primary_rails, dual_rails, axes, safe_dist) zero_pos = (0., 0.) - for acs in itertools.product(*zip(pcs, self.dc_carriages)): + for acs in itertools.product(*zip(pcs, dcs)): for c in acs: + if c is None: + continue dc_module.get_dc_rail_wrapper(c.get_rail()).activate( idex_modes.PRIMARY, zero_pos) - dc_rail = c.get_dual_carriage().get_rail() - dc_module.get_dc_rail_wrapper(dc_rail).inactivate(zero_pos) + dc = c.get_dual_carriage() + if dc is not None: + dc_module.get_dc_rail_wrapper(dc.get_rail()).inactivate( + zero_pos) self._check_kinematics(config.error) - for c in pcs: - dc_module.get_dc_rail_wrapper(c.get_rail()).activate( + for dc in self.dc_carriages: + dc_module.get_dc_rail_wrapper(dc.get_rail()).inactivate( + zero_pos) + for pc in self.primary_carriages: + if pc.get_axis() not in dc_axes: + continue + dc_module.get_dc_rail_wrapper(pc.get_rail()).activate( 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 @@ -152,25 +193,32 @@ class GenericCartesianKinematics: self.cmd_SET_STEPPER_CARRIAGES, desc=self.cmd_SET_STEPPER_CARRIAGES_help) def _load_kinematics(self, config): - carriages = {} + primary_carriages = [] for mcconfig in config.get_prefix_sections('carriage '): - c = MainCarriage(mcconfig) - axis = c.get_axis() - dups = [mc for mc in carriages.values() if mc.get_axis() == axis] - if dups: + primary_carriages.append(MainCarriage(mcconfig)) + for axis, axis_name in enumerate(VALID_AXES): + dups = [pc.get_name() for pc in primary_carriages + if pc.get_axis() == axis] + if len(dups) > 1: raise config.error( - "Axis '%s' referenced by multiple carriages (%s, %s)" - % ("xyz"[axis], c.get_name(), dups[0].get_name())) - carriages[c.get_name()] = c + "Axis '%s' is set for multiple primary carriages (%s)" + % (axis_name, ', '.join(dups))) + elif not dups: + raise config.error( + "No carriage defined for axis '%s'" % axis_name) dc_carriages = [] for dcconfig in config.get_prefix_sections('dual_carriage '): - dc_carriages.append(DualCarriage(dcconfig, carriages)) - for dc in dc_carriages: - name = dc.get_name() + dc_carriages.append(DualCarriage(dcconfig)) + carriages = {} + for carriage in primary_carriages + dc_carriages: + name = carriage.get_name() if name in carriages: raise config.error("Redefinition of carriage %s" % name) - carriages[name] = dc + carriages[name] = carriage + for dc in dc_carriages: + dc.resolve_primary_carriage(carriages) self.carriages = dict(carriages) + self.primary_carriages = primary_carriages self.dc_carriages = dc_carriages ec_carriages = [] for ecconfig in config.get_prefix_sections('extra_carriage '): @@ -207,16 +255,19 @@ class GenericCartesianKinematics: def get_steppers(self): return [s.get_stepper() for s in self.kin_steppers] def get_primary_carriages(self): - carriages = [None] * 3 - for carriage in self.carriages.values(): - a = carriage.get_axis() - if carriage.get_dual_carriage() is not None: + carriages = [] + for a in range(3): + c = None + if self.dc_module is not None and a in self.dc_module.get_axes(): primary_rail = self.dc_module.get_primary_rail(a) for c in self.carriages.values(): if c.get_rail() == primary_rail: - carriages[a] = c + break else: - carriages[a] = carriage + for c in self.primary_carriages: + if c.get_axis() == a: + break + carriages.append(c) return carriages def _get_kinematics_coeffs(self): matr = {s.get_name() : list(s.get_kin_coeffs()) @@ -227,9 +278,9 @@ class GenericCartesianKinematics: [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: - axis = dc.get_axis() - for c in [dc.get_dual_carriage(), dc]: + for c in self.carriages.values(): + axis = c.get_axis() + if axis in self.dc_module.get_axes(): m, o = self.dc_module.get_transform(c.get_rail()) for s in c.get_rail().get_steppers(): matr[s.get_name()][axis] *= m @@ -289,16 +340,14 @@ class GenericCartesianKinematics: homing_state.home_rails([rail], forcepos, homepos) def home(self, homing_state): self._check_kinematics(self.printer.command_error) + primary_carriages = self.get_primary_carriages() # Each axis is homed independently and in order for axis in homing_state.get_axes(): - for carriage in self.carriages.values(): - if carriage.get_axis() != axis: - continue - if carriage.get_dual_carriage() != None: - self.dc_module.home(homing_state, axis) - else: - self.home_axis(homing_state, axis, carriage.get_rail()) - break + if self.dc_module is not None and axis in self.dc_module.get_axes(): + self.dc_module.home(homing_state, axis) + else: + carriage = primary_carriages[axis] + 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): diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index bbe7bfa55..1cd2aa8bc 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -35,8 +35,8 @@ class HybridCoreXYKinematics: 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.)) + safe_dist=[dc_config.getfloat( + 'safe_distance', None, minval=0.)]) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) # Setup boundary checks diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index 3e2dd4788..03e889376 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -35,8 +35,8 @@ class HybridCoreXZKinematics: 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.)) + safe_dist=[dc_config.getfloat( + 'safe_distance', None, minval=0.)]) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) # Setup boundary checks diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py index 46b0be08b..4fe6df830 100644 --- a/klippy/kinematics/idex_modes.py +++ b/klippy/kinematics/idex_modes.py @@ -14,36 +14,37 @@ 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)] + safe_dist = list(safe_dist) + for i, dc in enumerate(dual_rails): + if dc is None or safe_dist[i] is not None: + continue + pc = primary_rails[i] + safe_dist[i] = min(abs(pc.position_min - dc.position_min), + abs(pc.position_max - dc.position_max)) + self.primary_mode_dcs = [None] * 3 + self.primary_rails = [] + for i, c in enumerate(primary_rails): + activate = self.primary_mode_dcs[axes[i]] is None + dc_rail = DualCarriagesRail( + printer, c, dual_rails[i], axes[i], safe_dist[i], + active=activate) + if activate: + self.primary_mode_dcs[axes[i]] = dc_rail + self.primary_rails.append(dc_rail) self.dual_rails = [ DualCarriagesRail(printer, c, primary_rails[i], - axes[i], active=False) + axes[i], safe_dist[i], active=False) + if c is not None else None 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]) + for c in self.primary_rails + self.dual_rails + if c is not None]) self.saved_states = {} - self.safe_dist = {} - for i, dc in enumerate(dual_rails): - axis = axes[i] - if isinstance(safe_dist, dict): - if axis in safe_dist: - self.safe_dist[axis] = safe_dist[axis] - continue - elif safe_dist is not None: - 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.axes = sorted(set(axes)) self.printer.add_object('dual_carriage', self) self.printer.register_event_handler("klippy:ready", self._handle_ready) gcode = self.printer.lookup_object('gcode') @@ -64,6 +65,8 @@ class DualCarriages: self.orig_stepper_kinematics = [] steppers = set() for rail in rails: + if rail is None: + continue c_steppers = rail.get_steppers() if not c_steppers: raise self.printer.config_error( @@ -80,10 +83,9 @@ class DualCarriages: 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 + if self.primary_mode_dcs[axis] is None: + return None + return self.primary_mode_dcs[axis].rail def get_dc_rail_wrapper(self, rail): for dc_rail in self.dc_rails.values(): if dc_rail.rail == rail: @@ -109,22 +111,27 @@ class DualCarriages: if target_dc.mode != PRIMARY: newpos = pos[:axis] + [target_dc.get_axis_position(pos)] \ + pos[axis+1:] + self.primary_mode_dcs[axis] = target_dc 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() - 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: - # 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() - for dc in dcs: - self.toggle_active_dc_rail(dc) - kin.home_axis(homing_state, axis, dc.rail) - # Restore the original rails ordering - self.activate_dc_mode(dcs[0], PRIMARY) + homing_rails = [r for r in self.primary_rails if r.axis == axis] + for dc_rail in homing_rails: + dcs = [dc for dc in self.dc_rails.values() + if dc_rail.rail in [dc.rail, dc.dual_rail]] + if len(dcs) > 1 and (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, so reversing the oder + dcs.reverse() + for dc in dcs: + self.toggle_active_dc_rail(dc) + kin.home_axis(homing_state, dc.axis, dc.rail) + # Restore the first rail as primary after all homed + self.activate_dc_mode(homing_rails[0], PRIMARY) def get_status(self, eventtime=None): status = {'carriages' : {dc.get_name() : dc.mode for dc in self.dc_rails.values()}} @@ -132,46 +139,62 @@ class DualCarriages: 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): + def get_kin_range(self, toolhead, axis): pos = toolhead.get_position() - dcs = [dc for dc in self.dc_rails.values() if dc.axis == axis] - axes_pos = [dc.get_axis_position(pos) for dc in dcs] - dc0_rail = dcs[0].rail - dc1_rail = dcs[1].rail - if mode != PRIMARY or dcs[0].is_active(): - range_min = dc0_rail.position_min - range_max = dc0_rail.position_max - else: - range_min = dc1_rail.position_min - range_max = dc1_rail.position_max - safe_dist = self.safe_dist[axis] - if not safe_dist: - return (range_min, range_max) + primary_carriage = self.primary_mode_dcs[axis] + if primary_carriage is None: + return (1.0, -1.0) + primary_pos = primary_carriage.get_axis_position(pos) + range_min = primary_carriage.rail.position_min + range_max = primary_carriage.rail.position_max + for carriage in self.dc_rails.values(): + if carriage.axis != axis: + continue + dcs = [carriage] + [dc for dc in self.dc_rails.values() + if carriage.rail is dc.dual_rail] + axes_pos = [dc.get_axis_position(pos) for dc in dcs] + # Check how dcs[0] affects the motion range of primary_carriage + if not dcs[0].is_active(): + continue + elif dcs[0].mode == COPY: + range_min = max(range_min, primary_pos + + dcs[0].rail.position_min - axes_pos[0]) + range_max = min(range_max, primary_pos + + dcs[0].rail.position_max - axes_pos[0]) + elif dcs[0].mode == MIRROR: + range_min = max(range_min, primary_pos + + axes_pos[0] - dcs[0].rail.position_max) + range_max = min(range_max, primary_pos + + axes_pos[0] - dcs[0].rail.position_min) + safe_dist = dcs[0].safe_dist + if not safe_dist or len(dcs) == 1: + continue + if dcs[0].mode == dcs[1].mode or \ + set((dcs[0].mode, dcs[1].mode)) == set((PRIMARY, COPY)): + # dcs[0] and dcs[1] carriages move in the same direction and + # cannot collide with each other + continue - 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) - elif mode == MIRROR: + # Compute how much dcs[0] can move towards dcs[1] + dcs_dist = axes_pos[1] - axes_pos[0] 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) + safe_move_dist = dcs_dist + safe_dist 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) - else: - # mode == PRIMARY - active_idx = 1 if dcs[1].is_active() else 0 - inactive_idx = 1 - active_idx - if self.get_dc_order(dcs[active_idx], dcs[inactive_idx]) > 0: - range_min = max(range_min, axes_pos[inactive_idx] + safe_dist) - else: - range_max = min(range_max, axes_pos[inactive_idx] - safe_dist) + safe_move_dist = dcs_dist - safe_dist + if dcs[1].is_active(): + safe_move_dist *= 0.5 + + if dcs[0].mode in (PRIMARY, COPY): + if self.get_dc_order(dcs[0], dcs[1]) > 0: + range_min = max(range_min, primary_pos + safe_move_dist) + else: + range_max = min(range_max, primary_pos + safe_move_dist) + else: # dcs[0].mode == MIRROR + if self.get_dc_order(dcs[0], dcs[1]) > 0: + range_max = min(range_max, primary_pos - safe_move_dist) + else: + range_min = max(range_min, primary_pos - safe_move_dist) + if range_min > range_max: # During multi-MCU homing it is possible that the carriage # position will end up below position_min or above position_max @@ -208,12 +231,13 @@ class DualCarriages: axis = dc.axis if mode == INACTIVE: dc.inactivate(toolhead.get_position()) + if self.primary_mode_dcs[axis] is dc: + self.primary_mode_dcs[axis] = None elif mode == PRIMARY: self.toggle_active_dc_rail(dc) else: - 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)) + kin.update_limits(axis, self.get_kin_range(toolhead, axis)) def _handle_ready(self): for dc_rail in self.dc_rails.values(): dc_rail.apply_transform() @@ -241,10 +265,9 @@ class DualCarriages: 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: + if self.primary_mode_dcs[dc_rail.axis] in [None, dc_rail]: raise gcmd.error( - "Mode=%s is not supported for carriage=%s" % ( - mode, dc_rail.get_name())) + "Must activate another carriage as PRIMARY first") curtime = self.printer.get_reactor().monotonic() kin = self.printer.lookup_object('toolhead').get_kinematics() axis = 'xyz'[dc_rail.axis] @@ -291,18 +314,21 @@ class DualCarriages: 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]]): - primary_ind, secondary_ind = dc_ind[0], dc_ind[1] - else: - primary_ind, secondary_ind = dc_ind[1], dc_ind[0] + abs_dl = [abs(dl[i]) for i in dc_ind] + primary_ind = dc_ind[abs_dl.index(max(abs_dl))] 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 - if dc_mode != INACTIVE: + for secondary_ind in dc_ind: + if secondary_ind == primary_ind: + continue + if min(abs(dl[primary_ind]), + abs(dl[secondary_ind])) < .000000001: + continue + if dl[primary_ind] * dl[secondary_ind] > 0: + dc_mode = COPY + else: + dc_mode = MIRROR dcs[secondary_ind].activate(dc_mode, cur_pos[primary_ind]) dcs[secondary_ind].override_axis_scaling( abs(dl[secondary_ind] / dl[primary_ind]), @@ -312,18 +338,26 @@ class DualCarriages: # Make sure the scaling coefficients are restored with the mode for dc in dcs: dc.inactivate(move_pos) + saved_modes = saved_state['carriage_modes'] + saved_primary_dcs = [dc for dc in self.dc_rails.values() + if saved_modes[dc.get_name()] == PRIMARY] + # First activate all primary carriages + for dc in saved_primary_dcs: + self.activate_dc_mode(dc, PRIMARY) + # Then set the modes the remaining carriages for dc in self.dc_rails.values(): - saved_mode = saved_state['carriage_modes'][dc.get_name()] - self.activate_dc_mode(dc, saved_mode) + if dc not in saved_primary_dcs: + self.activate_dc_mode(dc, saved_modes[dc.get_name()]) class DualCarriagesRail: ENC_AXES = [b'x', b'y'] - def __init__(self, printer, rail, dual_rail, axis, active): + def __init__(self, printer, rail, dual_rail, axis, safe_dist, active): self.printer = printer self.rail = rail self.dual_rail = dual_rail self.sks = [s.get_stepper_kinematics() for s in rail.get_steppers()] self.axis = axis + self.safe_dist = safe_dist self.mode = (INACTIVE, PRIMARY)[active] self.offset = 0. self.scale = 1. if active else 0. diff --git a/test/klippy/corexyuv.cfg b/test/klippy/corexyuv.cfg index e9b0cd02a..e6fd37006 100644 --- a/test/klippy/corexyuv.cfg +++ b/test/klippy/corexyuv.cfg @@ -24,6 +24,7 @@ primary_carriage: carriage_z endstop_pin: ^PD2 [dual_carriage carriage_u] +axis: x primary_carriage: carriage_x safe_distance: 70 position_endstop: 300 @@ -32,6 +33,7 @@ homing_speed: 50 endstop_pin: ^PE4 [dual_carriage carriage_v] +axis: y primary_carriage: carriage_y safe_distance: 50 position_endstop: 200 diff --git a/test/klippy/generic_cartesian_iqex.cfg b/test/klippy/generic_cartesian_iqex.cfg new file mode 100644 index 000000000..aa4c2f5cf --- /dev/null +++ b/test/klippy/generic_cartesian_iqex.cfg @@ -0,0 +1,386 @@ +# Test config for generic cartesian kinematics with quad independent extruders +[mcu] +serial: /dev/ttyACM0 + +[mcu extboard] +serial: /dev/ttyACM1 + +[carriage carriage_t0] +axis: x +position_endstop: 0 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG6 + +[dual_carriage carriage_t1] +primary_carriage: carriage_t0 +safe_distance: 70 +position_endstop: 300 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG9 + +[dual_carriage carriage_t2] +axis: x +position_endstop: 0 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG10 + +[dual_carriage carriage_t3] +primary_carriage: carriage_t2 +safe_distance: 70 +position_endstop: 300 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG11 + +[carriage carriage_gantry0_left] +axis: y +position_endstop: 0 +position_max: 200 +homing_speed: 50 +endstop_pin: PG6 + +[extra_carriage carriage_gantry0_right] +primary_carriage: carriage_gantry0_left +endstop_pin: PG9 + +[dual_carriage carriage_gantry1_left] +primary_carriage: carriage_gantry0_left +safe_distance: 50 +position_endstop: 200 +position_max: 200 +homing_speed: 50 +endstop_pin: PG10 + +[extra_carriage carriage_gantry1_right] +primary_carriage: carriage_gantry1_left +endstop_pin: PG11 + +[carriage carriage_z0] +axis: z +position_endstop: 0.5 +position_max: 100 +endstop_pin: PG12 + +[extra_carriage carriage_z1] +primary_carriage: carriage_z0 +endstop_pin: PG13 + +[extra_carriage carriage_z2] +primary_carriage: carriage_z0 +endstop_pin: PG14 + +[stepper stepper_t0_x] +carriages: carriage_t0 +step_pin: extboard:PF13 +dir_pin: extboard:PF12 +enable_pin: !extboard:PF14 +microsteps: 16 +rotation_distance: 40 + +[stepper stepper_t1_x] +carriages: carriage_t1 +step_pin: extboard:PG0 +dir_pin: extboard:PG1 +enable_pin: !extboard:PF15 +microsteps: 16 +rotation_distance: 40 + +[stepper stepper_t2_x] +carriages: carriage_t2 +step_pin: extboard:PF11 +dir_pin: extboard:PG3 +enable_pin: !extboard:PG5 +microsteps: 16 +rotation_distance: 40 + +[stepper stepper_t3_x] +carriages: carriage_t3 +step_pin: extboard:PG4 +dir_pin: extboard:PC1 +enable_pin: !extboard:PA2 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry0_left] +carriages: carriage_gantry0_left +step_pin: PF13 +dir_pin: PF12 +enable_pin: !PF14 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry0_right] +carriages: carriage_gantry0_right +step_pin: PG0 +dir_pin: PG1 +enable_pin: !PF15 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry1_left] +carriages: carriage_gantry1_left +step_pin: PF11 +dir_pin: PG3 +enable_pin: !PG5 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry1_right] +carriages: carriage_gantry1_right +step_pin: PG4 +dir_pin: PC1 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 40 + +[stepper z0] +carriages: carriage_z0 +step_pin: PF9 +dir_pin: PF10 +enable_pin: !PG2 +microsteps: 16 +rotation_distance: 8 + +[stepper z1] +carriages: carriage_z1 +step_pin: PC13 +dir_pin: PF0 +enable_pin: !PF1 +microsteps: 16 +rotation_distance: 8 + +[stepper z2] +carriages: carriage_z2 +step_pin: PE2 +dir_pin: PE3 +enable_pin: !PD4 +microsteps: 16 +rotation_distance: 8 + +[extruder] +step_pin: extboard:PF9 +dir_pin: extboard:PF10 +enable_pin: !extboard:PG2 +heater_pin: extboard:PA0 # HE0 +sensor_pin: extboard:PF4 # T0 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder1] +step_pin: extboard:PC13 +dir_pin: extboard:PF0 +enable_pin: !extboard:PF1 +heater_pin: extboard:PA3 # HE1 +sensor_pin: extboard:PF5 # T1 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder2] +step_pin: extboard:PE2 +dir_pin: extboard:PE3 +enable_pin: !extboard:PD4 +heater_pin: extboard:PB0 # HE2 +sensor_pin: extboard:PF6 # T2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder3] +step_pin: extboard:PE6 +dir_pin: extboard:PA14 +enable_pin: !extboard:PE0 +heater_pin: extboard:PB11 # HE3 +sensor_pin: extboard:PF7 # T3 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[gcode_macro PARK_EXTRUDERS] +gcode: + G90 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + G1 Y{printer.configfile.settings["dual_carriage carriage_gantry1_left"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 + G1 X{printer.configfile.settings["dual_carriage carriage_t3"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + +[gcode_macro T0] +gcode: + PARK_EXTRUDERS + ACTIVATE_EXTRUDER EXTRUDER=extruder + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + +[gcode_macro T1] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder1 + ACTIVATE_EXTRUDER EXTRUDER=extruder1 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + +[gcode_macro T2] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder2 + ACTIVATE_EXTRUDER EXTRUDER=extruder2 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + +[gcode_macro T3] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder3 + ACTIVATE_EXTRUDER EXTRUDER=extruder3 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + +[gcode_macro SET_COPY_MODE] +gcode: + G90 + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1_left"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + {% set x_max = [printer.configfile.settings["dual_carriage carriage_t3"].position_max, printer.configfile.settings["dual_carriage carriage_t1"].position_max]|min %} + {% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %} + {% set x_center = 0.5 * (x_max + x_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder + +[gcode_macro SET_MIRROR_MODE1] +gcode: + G90 + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1_left"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 + G1 X{printer.configfile.settings["dual_carriage carriage_t3"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder + +[gcode_macro SET_MIRROR_MODE2] +gcode: + G90 + {% set x_max = [printer.configfile.settings["dual_carriage carriage_t3"].position_max, printer.configfile.settings["dual_carriage carriage_t1"].position_max]|min %} + {% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %} + {% set x_center = 0.5 * (x_max + x_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + G1 Y{printer.configfile.settings["dual_carriage carriage_gantry1_left"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left MODE=MIRROR + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder + +[heater_bed] +heater_pin: PA1 +sensor_pin: PF3 # TB +sensor_type: ATC Semitec 104GT-2 +control: watermark +min_temp: 0 +max_temp: 130 + +[fan] +pin: PA8 + +[printer] +kinematics: generic_cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[input_shaper] diff --git a/test/klippy/generic_cartesian_iqex.test b/test/klippy/generic_cartesian_iqex.test new file mode 100644 index 000000000..b317dbf21 --- /dev/null +++ b/test/klippy/generic_cartesian_iqex.test @@ -0,0 +1,71 @@ +# Test cases on printers with quad independent extruders +DICTIONARY stm32h723.dict extboard=stm32h723.dict +CONFIG generic_cartesian_iqex.cfg + +# First home the printer +G90 +M83 +G28 + +# Perform a dummy move +G1 X10 Y20 Z10 F6000 +G1 X11 E0.1 F3000 + +# Test other tools +T1 +G1 X120 Y50 F6000 +G1 X119 E0.1 F3000 + +T2 +G1 X200 Y70 F6000 +G1 X199 E0.1 F3000 + +T3 +G1 X70 Y50 F6000 +G1 X71 E0.1 F3000 + +# Go back to main tool +T0 +G1 X20 Y100 F6000 + +# Save dual carriage state +SAVE_DUAL_CARRIAGE_STATE + +G1 Y100 F6000 + +T2 +# Activate the dual carriage on Y axis +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left +G1 X10 Y150 F6000 + +# Restore dual carriage state +RESTORE_DUAL_CARRIAGE_STATE + +QUERY_ENDSTOPS + +# Configure input shaper +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left +SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 +SET_INPUT_SHAPER SHAPER_TYPE_X=ei SHAPER_FREQ_X=50 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=80 +SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 +SET_INPUT_SHAPER SHAPER_TYPE_X=ei SHAPER_FREQ_X=45 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=80 +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left +SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 +SET_INPUT_SHAPER SHAPER_TYPE_X=mzv SHAPER_FREQ_X=50 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=70 +SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 +SET_INPUT_SHAPER SHAPER_TYPE_X=zvd SHAPER_FREQ_X=55 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=70 + +T0 +G1 X100 Y150 F6000 + +SET_COPY_MODE +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 + +SET_MIRROR_MODE1 +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 + +SET_MIRROR_MODE2 +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 diff --git a/test/klippy/generic_cartesian_itex.cfg b/test/klippy/generic_cartesian_itex.cfg new file mode 100644 index 000000000..dc0ae115a --- /dev/null +++ b/test/klippy/generic_cartesian_itex.cfg @@ -0,0 +1,320 @@ +# Test config for generic cartesian kinematics with triple independent extruders +[mcu] +serial: /dev/ttyACM0 + +[mcu extboard] +serial: /dev/ttyACM1 + +[carriage carriage_t0] +axis: x +position_endstop: 0 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG6 + +[dual_carriage carriage_t1] +primary_carriage: carriage_t0 +safe_distance: 70 +position_endstop: 300 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG9 + +[dual_carriage carriage_t2] +axis: x +position_endstop: 0 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG10 + +[carriage carriage_gantry0_left] +axis: y +position_endstop: 0 +position_max: 200 +homing_speed: 50 +endstop_pin: PG6 + +[extra_carriage carriage_gantry0_right] +primary_carriage: carriage_gantry0_left +endstop_pin: PG9 + +[dual_carriage carriage_gantry1] +primary_carriage: carriage_gantry0_left +safe_distance: 50 +position_endstop: 200 +position_max: 200 +homing_speed: 50 +endstop_pin: PG10 + +[carriage carriage_z0] +axis: z +position_endstop: 0.5 +position_max: 100 +endstop_pin: PG12 + +[extra_carriage carriage_z1] +primary_carriage: carriage_z0 +endstop_pin: PG13 + +[extra_carriage carriage_z2] +primary_carriage: carriage_z0 +endstop_pin: PG14 + +[stepper stepper_t0_x] +carriages: carriage_t0 +step_pin: extboard:PF13 +dir_pin: extboard:PF12 +enable_pin: !extboard:PF14 +microsteps: 16 +rotation_distance: 40 + +[stepper stepper_t1_x] +carriages: carriage_t1 +step_pin: extboard:PG0 +dir_pin: extboard:PG1 +enable_pin: !extboard:PF15 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry0_left] +carriages: carriage_gantry0_left +step_pin: PF13 +dir_pin: PF12 +enable_pin: !PF14 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry0_right] +carriages: carriage_gantry0_right +step_pin: PG0 +dir_pin: PG1 +enable_pin: !PF15 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry1_a] +carriages: carriage_t2-carriage_gantry1 +step_pin: PF11 +dir_pin: PG3 +enable_pin: !PG5 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry1_b] +carriages: carriage_t2+carriage_gantry1 +step_pin: PG4 +dir_pin: PC1 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 40 + +[stepper z0] +carriages: carriage_z0 +step_pin: PF9 +dir_pin: PF10 +enable_pin: !PG2 +microsteps: 16 +rotation_distance: 8 + +[stepper z1] +carriages: carriage_z1 +step_pin: PC13 +dir_pin: PF0 +enable_pin: !PF1 +microsteps: 16 +rotation_distance: 8 + +[stepper z2] +carriages: carriage_z2 +step_pin: PE2 +dir_pin: PE3 +enable_pin: !PD4 +microsteps: 16 +rotation_distance: 8 + +[extruder] +step_pin: extboard:PF9 +dir_pin: extboard:PF10 +enable_pin: !extboard:PG2 +heater_pin: extboard:PA0 # HE0 +sensor_pin: extboard:PF4 # T0 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder1] +step_pin: extboard:PC13 +dir_pin: extboard:PF0 +enable_pin: !extboard:PF1 +heater_pin: extboard:PA3 # HE1 +sensor_pin: extboard:PF5 # T1 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder2] +step_pin: extboard:PE2 +dir_pin: extboard:PE3 +enable_pin: !extboard:PD4 +heater_pin: extboard:PB0 # HE2 +sensor_pin: extboard:PF6 # T2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[gcode_macro PARK_EXTRUDERS] +gcode: + G90 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + G1 Y{printer.configfile.settings["dual_carriage carriage_gantry1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + +[gcode_macro T0] +gcode: + PARK_EXTRUDERS + ACTIVATE_EXTRUDER EXTRUDER=extruder + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + +[gcode_macro T1] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder1 + ACTIVATE_EXTRUDER EXTRUDER=extruder1 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + +[gcode_macro T2] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder2 + ACTIVATE_EXTRUDER EXTRUDER=extruder2 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + +[gcode_macro SET_COPY_MODE] +gcode: + G90 + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + {% set x_max = printer.configfile.settings["dual_carriage carriage_t1"].position_max %} + {% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %} + {% set x_center = 0.5 * (x_max + x_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + +[gcode_macro SET_MIRROR_MODE1] +gcode: + G90 + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + {% set x_max = printer.configfile.settings["dual_carriage carriage_t1"].position_max %} + {% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %} + {% set x_center = 0.5 * (x_max + x_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + +[gcode_macro SET_MIRROR_MODE2] +gcode: + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + G90 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + +[heater_bed] +heater_pin: PA1 +sensor_pin: PF3 # TB +sensor_type: ATC Semitec 104GT-2 +control: watermark +min_temp: 0 +max_temp: 130 + +[fan] +pin: PA8 + +[printer] +kinematics: generic_cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[input_shaper] diff --git a/test/klippy/generic_cartesian_itex.test b/test/klippy/generic_cartesian_itex.test new file mode 100644 index 000000000..bcf80771d --- /dev/null +++ b/test/klippy/generic_cartesian_itex.test @@ -0,0 +1,65 @@ +# Test cases on printers with triple independent extruders +DICTIONARY stm32h723.dict extboard=stm32h723.dict +CONFIG generic_cartesian_itex.cfg + +# First home the printer +G90 +M83 +G28 + +# Perform a dummy move +G1 X10 Y20 Z10 F6000 +G1 X11 E0.1 F3000 + +# Test other tools +T1 +G1 X120 Y50 F6000 +G1 X119 E0.1 F3000 + +T2 +G1 X200 Y70 F6000 +G1 X199 E0.1 F3000 + +# Go back to main tool +T0 +G1 X20 Y100 F6000 + +# Save dual carriage state +SAVE_DUAL_CARRIAGE_STATE + +G1 Y100 F6000 + +T2 +# Activate the dual carriage on Y axis +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 +G1 X10 Y150 F6000 + +# Restore dual carriage state +RESTORE_DUAL_CARRIAGE_STATE + +QUERY_ENDSTOPS + +# Configure input shaper +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 +SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 +SET_INPUT_SHAPER SHAPER_TYPE_X=ei SHAPER_FREQ_X=45 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=80 +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left +SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 +SET_INPUT_SHAPER SHAPER_TYPE_X=mzv SHAPER_FREQ_X=50 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=70 +SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 +SET_INPUT_SHAPER SHAPER_TYPE_X=zvd SHAPER_FREQ_X=55 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=70 + +T0 +G1 X100 Y150 F6000 + +SET_COPY_MODE +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 + +SET_MIRROR_MODE1 +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 + +SET_MIRROR_MODE2 +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000