kinematics: Add dual_carriage to hybrid-corexyz (#4296)

- Add dual_carriage abilities for hybrid-corexy and hybrid-corexz
- Introduce the module idex_mode
- Fix add_stepper to the correct rail in hybrid-corexy

Signed-off-by: Fabrice GALLET <tircown@gmail.com>
This commit is contained in:
Tircown 2021-06-28 00:37:05 +02:00 committed by GitHub
parent 274d52729a
commit 4d559633e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 233 additions and 9 deletions

View File

@ -322,6 +322,15 @@ The following information is available in the `toolhead` object
the printer had to be paused because the toolhead moved faster than the printer had to be paused because the toolhead moved faster than
moves could be read from the G-Code input. moves could be read from the G-Code input.
# dual_carriage
The following information is available in
[dual_carriage](Config_Reference.md#dual_carriage)
on a hybrid_corexy or hybrid_corexz robot
- `mode`: The current mode. Possible values are: "FULL_CONTROL"
- `active_carriage`: The current active carriage.
Possible values are: "CARRIAGE_0", "CARRIAGE_1"
# virtual_sdcard # virtual_sdcard
The following information is available in the The following information is available in the

View File

@ -80,6 +80,7 @@ defs_trapq = """
defs_kin_cartesian = """ defs_kin_cartesian = """
struct stepper_kinematics *cartesian_stepper_alloc(char axis); struct stepper_kinematics *cartesian_stepper_alloc(char axis);
struct stepper_kinematics *cartesian_reverse_stepper_alloc(char axis);
""" """
defs_kin_corexy = """ defs_kin_corexy = """

View File

@ -49,3 +49,42 @@ cartesian_stepper_alloc(char axis)
} }
return sk; return sk;
} }
static double
cart_reverse_stepper_x_calc_position(struct stepper_kinematics *sk
, struct move *m, double move_time)
{
return -move_get_coord(m, move_time).x;
}
static double
cart_reverse_stepper_y_calc_position(struct stepper_kinematics *sk
, struct move *m, double move_time)
{
return -move_get_coord(m, move_time).y;
}
static double
cart_reverse_stepper_z_calc_position(struct stepper_kinematics *sk
, struct move *m, double move_time)
{
return -move_get_coord(m, move_time).z;
}
struct stepper_kinematics * __visible
cartesian_reverse_stepper_alloc(char axis)
{
struct stepper_kinematics *sk = malloc(sizeof(*sk));
memset(sk, 0, sizeof(*sk));
if (axis == 'x') {
sk->calc_position_cb = cart_reverse_stepper_x_calc_position;
sk->active_flags = AF_X;
} else if (axis == 'y') {
sk->calc_position_cb = cart_reverse_stepper_y_calc_position;
sk->active_flags = AF_Y;
} else if (axis == 'z') {
sk->calc_position_cb = cart_reverse_stepper_z_calc_position;
sk->active_flags = AF_Z;
}
return sk;
}

View File

@ -4,7 +4,7 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import logging import logging
import stepper import stepper, idex_modes
# The hybrid-corexy kinematic is also known as Markforged kinematics # The hybrid-corexy kinematic is also known as Markforged kinematics
class HybridCoreXYKinematics: class HybridCoreXYKinematics:
@ -15,7 +15,7 @@ class HybridCoreXYKinematics:
self.rails = [ stepper.PrinterRail(config.getsection('stepper_x')), self.rails = [ stepper.PrinterRail(config.getsection('stepper_x')),
stepper.LookupMultiRail(config.getsection('stepper_y')), stepper.LookupMultiRail(config.getsection('stepper_y')),
stepper.LookupMultiRail(config.getsection('stepper_z'))] stepper.LookupMultiRail(config.getsection('stepper_z'))]
self.rails[2].get_endstops()[0][0].add_stepper( self.rails[1].get_endstops()[0][0].add_stepper(
self.rails[0].get_steppers()[0]) self.rails[0].get_steppers()[0])
self.rails[0].setup_itersolve('corexy_stepper_alloc', '-') self.rails[0].setup_itersolve('corexy_stepper_alloc', '-')
self.rails[1].setup_itersolve('cartesian_stepper_alloc', 'y') self.rails[1].setup_itersolve('cartesian_stepper_alloc', 'y')
@ -23,6 +23,26 @@ class HybridCoreXYKinematics:
ranges = [r.get_range() for r in self.rails] ranges = [r.get_range() for r in self.rails]
self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.) self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.) self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
self.dc_module = None
if config.has_section('dual_carriage'):
dc_config = config.getsection('dual_carriage')
# dummy for cartesian config users
dc_config.getchoice('axis', {'x': 'x'}, default='x')
# setup second dual carriage rail
self.rails.append(stepper.PrinterRail(dc_config))
self.rails[1].get_endstops()[0][0].add_stepper(
self.rails[3].get_steppers()[0])
self.rails[3].setup_itersolve('cartesian_stepper_alloc', 'y')
dc_rail_0 = idex_modes.DualCarriagesRail(
self.printer, self.rails[0], axis=0, active=True,
stepper_alloc_active=('corexy_stepper_alloc','-'),
stepper_alloc_inactive=('cartesian_reverse_stepper_alloc','y'))
dc_rail_1 = idex_modes.DualCarriagesRail(
self.printer, self.rails[3], axis=0, active=False,
stepper_alloc_active=('corexy_stepper_alloc','+'),
stepper_alloc_inactive=('cartesian_stepper_alloc','y'))
self.dc_module = idex_modes.DualCarriages(self.printer,
dc_rail_0, dc_rail_1, axis=0)
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps) toolhead.register_step_generator(s.generate_steps)
@ -39,7 +59,15 @@ class HybridCoreXYKinematics:
return [s for rail in self.rails for s in rail.get_steppers()] return [s for rail in self.rails for s in rail.get_steppers()]
def calc_position(self, stepper_positions): def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails] pos = [stepper_positions[rail.get_name()] for rail in self.rails]
return [pos[0] + pos[1], pos[1], pos[2]] if (self.dc_module is not None and 'CARRIAGE_1' == \
self.dc_module.get_status()['active_carriage']):
return [pos[0] - pos[1], pos[1], pos[2]]
else:
return [pos[0] + pos[1], pos[1], pos[2]]
def update_limits(self, i, range):
self.limits[i] = range
def override_rail(self, i, rail):
self.rails[i] = rail
def set_position(self, newpos, homing_axes): def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails): for i, rail in enumerate(self.rails):
rail.set_position(newpos) rail.set_position(newpos)
@ -62,7 +90,14 @@ class HybridCoreXYKinematics:
homing_state.home_rails([rail], forcepos, homepos) homing_state.home_rails([rail], forcepos, homepos)
def home(self, homing_state): def home(self, homing_state):
for axis in homing_state.get_axes(): for axis in homing_state.get_axes():
self._home_axis(homing_state, axis, self.rails[axis]) if (self.dc_module is not None and axis == 0):
self.dc_module.save_idex_state()
for i in [0,1]:
self.dc_module.toggle_active_dc_rail(i)
self._home_axis(homing_state, axis, self.rails[0])
self.dc_module.restore_idex_state()
else:
self._home_axis(homing_state, axis, self.rails[axis])
def _motor_off(self, print_time): def _motor_off(self, print_time):
self.limits = [(1.0, -1.0)] * 3 self.limits = [(1.0, -1.0)] * 3
def _check_endstops(self, move): def _check_endstops(self, move):
@ -93,7 +128,7 @@ class HybridCoreXYKinematics:
return { return {
'homed_axes': "".join(axes), 'homed_axes': "".join(axes),
'axis_minimum': self.axes_min, 'axis_minimum': self.axes_min,
'axis_maximum': self.axes_max, 'axis_maximum': self.axes_max
} }
def load_kinematics(toolhead, config): def load_kinematics(toolhead, config):

View File

@ -4,7 +4,7 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import logging import logging
import stepper import stepper, idex_modes
# The hybrid-corexz kinematic is also known as Markforged kinematics # The hybrid-corexz kinematic is also known as Markforged kinematics
class HybridCoreXZKinematics: class HybridCoreXZKinematics:
@ -23,6 +23,26 @@ class HybridCoreXZKinematics:
ranges = [r.get_range() for r in self.rails] ranges = [r.get_range() for r in self.rails]
self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.) self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.) self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
self.dc_module = None
if config.has_section('dual_carriage'):
dc_config = config.getsection('dual_carriage')
# dummy for cartesian config users
dc_config.getchoice('axis', {'x': 'x'}, default='x')
# setup second dual carriage rail
self.rails.append(stepper.PrinterRail(dc_config))
self.rails[2].get_endstops()[0][0].add_stepper(
self.rails[3].get_steppers()[0])
self.rails[3].setup_itersolve('cartesian_stepper_alloc', 'z')
dc_rail_0 = idex_modes.DualCarriagesRail(
self.printer, self.rails[0], axis=0, active=True,
stepper_alloc_active=('corexz_stepper_alloc','-'),
stepper_alloc_inactive=('cartesian_reverse_stepper_alloc','z'))
dc_rail_1 = idex_modes.DualCarriagesRail(
self.printer, self.rails[3], axis=0, active=False,
stepper_alloc_active=('corexz_stepper_alloc','+'),
stepper_alloc_inactive=('cartesian_stepper_alloc','z'))
self.dc_module = idex_modes.DualCarriages(self.printer,
dc_rail_0, dc_rail_1, axis=0)
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps) toolhead.register_step_generator(s.generate_steps)
@ -39,7 +59,15 @@ class HybridCoreXZKinematics:
return [s for rail in self.rails for s in rail.get_steppers()] return [s for rail in self.rails for s in rail.get_steppers()]
def calc_position(self, stepper_positions): def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails] pos = [stepper_positions[rail.get_name()] for rail in self.rails]
return [pos[0] + pos[2], pos[1], pos[2]] if (self.dc_module is not None and 'CARRIAGE_1' == \
self.dc_module.get_status()['active_carriage']):
return [pos[0] - pos[2], pos[1], pos[2]]
else:
return [pos[0] + pos[2], pos[1], pos[2]]
def update_limits(self, i, range):
self.limits[i] = range
def override_rail(self, i, rail):
self.rails[i] = rail
def set_position(self, newpos, homing_axes): def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails): for i, rail in enumerate(self.rails):
rail.set_position(newpos) rail.set_position(newpos)
@ -62,7 +90,14 @@ class HybridCoreXZKinematics:
homing_state.home_rails([rail], forcepos, homepos) homing_state.home_rails([rail], forcepos, homepos)
def home(self, homing_state): def home(self, homing_state):
for axis in homing_state.get_axes(): for axis in homing_state.get_axes():
self._home_axis(homing_state, axis, self.rails[axis]) if (self.dc_module is not None and axis == 0):
self.dc_module.save_idex_state()
for i in [0,1]:
self.dc_module.toggle_active_dc_rail(i)
self._home_axis(homing_state, axis, self.rails[0])
self.dc_module.restore_idex_state()
else:
self._home_axis(homing_state, axis, self.rails[axis])
def _motor_off(self, print_time): def _motor_off(self, print_time):
self.limits = [(1.0, -1.0)] * 3 self.limits = [(1.0, -1.0)] * 3
def _check_endstops(self, move): def _check_endstops(self, move):
@ -93,7 +128,7 @@ class HybridCoreXZKinematics:
return { return {
'homed_axes': "".join(axes), 'homed_axes': "".join(axes),
'axis_minimum': self.axes_min, 'axis_minimum': self.axes_min,
'axis_maximum': self.axes_max, 'axis_maximum': self.axes_max
} }
def load_kinematics(toolhead, config): def load_kinematics(toolhead, config):

View File

@ -0,0 +1,105 @@
# Support for duplication and mirroring modes for IDEX printers
#
# Copyright (C) 2021 Fabrice Gallet <tircown@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math
class DualCarriages:
def __init__(self, printer, rail_0, rail_1, axis):
self.printer = printer
self.axis = axis
self.dc = (rail_0, rail_1)
self.saved_state = None
self.printer.add_object('dual_carriage', self)
gcode = self.printer.lookup_object('gcode')
gcode.register_command(
'SET_DUAL_CARRIAGE', self.cmd_SET_DUAL_CARRIAGE,
desc=self.cmd_SET_DUAL_CARRIAGE_help)
def toggle_active_dc_rail(self, index):
toolhead = self.printer.lookup_object('toolhead')
toolhead.flush_step_generation()
pos = toolhead.get_position()
kin = toolhead.get_kinematics()
for i, dc in enumerate(self.dc):
dc_rail = dc.get_rail()
if i != index:
dc.inactivate(pos)
kin.override_rail(3, dc_rail)
elif dc.is_active() is False:
newpos = pos[:self.axis] + [dc.axis_position] \
+ pos[self.axis+1:]
dc.activate(newpos)
kin.override_rail(self.axis, dc_rail)
toolhead.set_position(newpos)
kin.update_limits(self.axis, dc_rail.get_range())
def get_status(self, eventtime):
dc0, dc1 = self.dc
if (dc0.is_active() is True):
return { 'mode': 'FULL_CONTROL', 'active_carriage': 'CARRIAGE_0' }
else:
return { 'mode': 'FULL_CONTROL', 'active_carriage': 'CARRIAGE_1' }
def save_idex_state(self):
dc0, dc1 = self.dc
if (dc0.is_active() is True):
mode, active_carriage = ('FULL_CONTROL', 'CARRIAGE_0')
else:
mode, active_carriage = ('FULL_CONTROL', 'CARRIAGE_1')
self.saved_state = {
'mode': mode,
'active_carriage': active_carriage,
'axis_positions': (dc0.axis_position, dc1.axis_position)
}
def restore_idex_state(self):
if self.saved_state is not None:
# set carriage 0 active
if (self.saved_state['active_carriage'] == 'CARRIAGE_0'
and self.dc[0].is_active() is False):
self.toggle_active_dc_rail(0)
# set carriage 1 active
elif (self.saved_state['active_carriage'] == 'CARRIAGE_1'
and self.dc[1].is_active() is False):
self.toggle_active_dc_rail(1)
cmd_SET_DUAL_CARRIAGE_help = "Set which carriage is active"
def cmd_SET_DUAL_CARRIAGE(self, gcmd):
index = gcmd.get_int('CARRIAGE', minval=0, maxval=1)
if (not(self.dc[0].is_active() == self.dc[1].is_active() == True)
and self.dc[index].is_active() is False):
self.toggle_active_dc_rail(index)
class DualCarriagesRail:
ACTIVE=1
INACTIVE=2
def __init__(self, printer, rail, axis, active, stepper_alloc_active,
stepper_alloc_inactive=None):
self.printer = printer
self.rail = rail
self.axis = axis
self.status = (self.INACTIVE, self.ACTIVE)[active]
self.stepper_alloc_active = stepper_alloc_active
self.stepper_alloc_inactive = stepper_alloc_inactive
self.axis_position = -1
def _stepper_alloc(self, position, active=True):
toolhead = self.printer.lookup_object('toolhead')
self.axis_position = position[self.axis]
self.rail.set_trapq(None)
if active is True:
self.status = self.ACTIVE
if self.stepper_alloc_active is not None:
self.rail.setup_itersolve(*self.stepper_alloc_active)
self.rail.set_position(position)
self.rail.set_trapq(toolhead.get_trapq())
else:
self.status = self.INACTIVE
if self.stepper_alloc_inactive is not None:
self.rail.setup_itersolve(*self.stepper_alloc_inactive)
self.rail.set_position(position)
self.rail.set_trapq(toolhead.get_trapq())
def get_rail(self):
return self.rail
def is_active(self):
return self.status == self.ACTIVE
def activate(self, position):
self._stepper_alloc(position, active=True)
def inactivate(self, position):
self._stepper_alloc(position, active=False)