generic_cartesian: Full IQEX printers support (#7165)
Some checks failed
Build test / build (push) Waiting to run
klipper3d deploy / deploy (push) Has been cancelled

Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
This commit is contained in:
Dmitry Butyugin 2026-02-04 22:54:05 +01:00 committed by GitHub
parent ca7d90084c
commit 4c89f7f826
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1099 additions and 164 deletions

View file

@ -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:

View file

@ -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).

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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]

View file

@ -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