idex_modes: COPY and MIRROR mode implementation (#6297)
COPY and MIRROR mode implementation Correctly apply input shaper params to new dual_carriage Added SAVE_/RESTORE_IDEX_STATE commands Documentation updates for the new IDEX modes Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
This commit is contained in:
parent
ea330717cd
commit
36be1cfc51
|
@ -62,6 +62,8 @@ endstop_pin: ^ar2
|
|||
position_endstop: 200
|
||||
position_max: 200
|
||||
homing_speed: 50
|
||||
# A minimum distance between the carriages to enforce
|
||||
safe_distance: 40
|
||||
|
||||
[extruder1]
|
||||
step_pin: ar36
|
||||
|
@ -94,3 +96,37 @@ gcode:
|
|||
ACTIVATE_EXTRUDER EXTRUDER=extruder1
|
||||
SET_DUAL_CARRIAGE CARRIAGE=1
|
||||
SET_GCODE_OFFSET Y=15
|
||||
|
||||
# A helper script to activate copy mode
|
||||
[gcode_macro ACTIVATE_COPY_MODE]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=0 MODE=PRIMARY
|
||||
G1 X0
|
||||
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||
SET_DUAL_CARRIAGE CARRIAGE=1 MODE=PRIMARY
|
||||
G1 X100
|
||||
SET_DUAL_CARRIAGE CARRIAGE=1 MODE=COPY
|
||||
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
|
||||
|
||||
# A helper script to activate mirror mode
|
||||
[gcode_macro ACTIVATE_MIRROR_MODE]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=0 MODE=PRIMARY
|
||||
G1 X0
|
||||
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||
SET_DUAL_CARRIAGE CARRIAGE=1 MODE=PRIMARY
|
||||
G1 X200
|
||||
SET_DUAL_CARRIAGE CARRIAGE=1 MODE=MIRROR
|
||||
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
|
||||
|
||||
## An optional input shaper support
|
||||
#[input_shaper]
|
||||
## The section is intentionally empty
|
||||
#
|
||||
#[delayed_gcode init_shaper]
|
||||
#initial_duration: 0.1
|
||||
#gcode:
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=1
|
||||
# SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_shaper> SHAPER_FREQ_X=<dual_carriage_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=0
|
||||
# SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_shaper> SHAPER_FREQ_X=<primary_carriage_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
|
||||
|
|
|
@ -8,6 +8,11 @@ All dates in this document are approximate.
|
|||
|
||||
## Changes
|
||||
|
||||
20230729: The exported status for `dual_carriage` is changed. Instead of
|
||||
exporting `mode` and `active_carriage`, the individual modes for each
|
||||
carriage are exported as `printer.dual_carriage.carriage_0` and
|
||||
`printer.dual_carriage.carriage_1`.
|
||||
|
||||
20230619: The `relative_reference_index` option has been deprecated
|
||||
and superceded by the `zero_reference_position` option. Refer to the
|
||||
[Bed Mesh Documentation](./Bed_Mesh.md#the-deprecated-relative_reference_index)
|
||||
|
|
|
@ -2007,14 +2007,24 @@ for an example configuration.
|
|||
|
||||
### [dual_carriage]
|
||||
|
||||
Support for cartesian printers with dual carriages on a single
|
||||
axis. The active carriage is set via the SET_DUAL_CARRIAGE extended
|
||||
g-code command. The "SET_DUAL_CARRIAGE CARRIAGE=1" command will
|
||||
activate the carriage defined in this section (CARRIAGE=0 will return
|
||||
activation to the primary carriage). Dual carriage support is
|
||||
typically combined with extra extruders - the SET_DUAL_CARRIAGE
|
||||
command is often called at the same time as the ACTIVATE_EXTRUDER
|
||||
command. Be sure to park the carriages during deactivation.
|
||||
Support for cartesian and hybrid_corexy/z printers with dual carriages
|
||||
on a single axis. The carriage mode can be set via the
|
||||
SET_DUAL_CARRIAGE extended g-code command. For example,
|
||||
"SET_DUAL_CARRIAGE CARRIAGE=1" command will activate the carriage defined
|
||||
in this section (CARRIAGE=0 will return activation to the primary carriage).
|
||||
Dual carriage support is typically combined with extra extruders - the
|
||||
SET_DUAL_CARRIAGE command is often called at the same time as the
|
||||
ACTIVATE_EXTRUDER command. Be sure to park the carriages during deactivation.
|
||||
Additionally, one could use "SET_DUAL_CARRIAGE CARRIAGE=1 MODE=COPY" or
|
||||
"SET_DUAL_CARRIAGE CARRIAGE=1 MODE=MIRROR" commands to activate either copying
|
||||
or mirroring mode of the dual carriage, in which case it will follow the
|
||||
motion of the carriage 0 accordingly. These commands can be used to print
|
||||
two parts simultaneously - either two identical parts (in COPY mode) or
|
||||
mirrored parts (in MIRROR mode). Note that COPY and MIRROR modes also require
|
||||
appropriate configuration of the extruder on the dual carriage, which can
|
||||
typically be achieved with
|
||||
"SYNC_EXTRUDER_MOTION MOTION_QUEUE=extruder EXTRUDER=<dual_carriage_extruder>"
|
||||
or a similar command.
|
||||
|
||||
See [sample-idex.cfg](../config/sample-idex.cfg) for an example
|
||||
configuration.
|
||||
|
@ -2024,6 +2034,15 @@ configuration.
|
|||
axis:
|
||||
# The axis this extra carriage is on (either x or y). This parameter
|
||||
# must be provided.
|
||||
#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
|
||||
# closer than the specified limit, such a command will be rejected with an
|
||||
# error. If safe_distance is not provided, it will be inferred from
|
||||
# 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 carraiges), the carriages proximity
|
||||
# checks will be disabled.
|
||||
#step_pin:
|
||||
#dir_pin:
|
||||
#enable_pin:
|
||||
|
|
|
@ -310,9 +310,33 @@ The following command is available when the
|
|||
enabled.
|
||||
|
||||
#### SET_DUAL_CARRIAGE
|
||||
`SET_DUAL_CARRIAGE CARRIAGE=[0|1]`: This command will set the active
|
||||
carriage. It is typically invoked from the activate_gcode and
|
||||
deactivate_gcode fields in a multiple extruder configuration.
|
||||
`SET_DUAL_CARRIAGE CARRIAGE=[0|1] [MODE=[PRIMARY|COPY|MIRROR]]`:
|
||||
This command will change the mode of the specified carriage.
|
||||
If no `MODE` is provided it defaults to `PRIMARY`. 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 `CARRIAGE=1`. When set to either of these
|
||||
modes, carriage 1 will then track the subsequent moves of the carriage 0
|
||||
and either copy relative movements of it (in `COPY` mode) or execute them
|
||||
in the opposite (mirror) direction (in `MIRROR` mode).
|
||||
|
||||
#### SAVE_DUAL_CARRIAGE_STATE
|
||||
`SAVE_DUAL_CARRIAGE_STATE [NAME=<state_name>]`: Save the current positions
|
||||
of the dual carriages and their modes. Saving and restoring DUAL_CARRIAGE
|
||||
state can be useful in scripts and macros, as well as in homing routine
|
||||
overrides. If NAME is provided it allows one to name the saved state
|
||||
to the given string. If NAME is not provided it defaults to "default".
|
||||
|
||||
#### RESTORE_DUAL_CARRIAGE_STATE
|
||||
`RESTORE_DUAL_CARRIAGE_STATE [NAME=<state_name>] [MOVE=[0|1] [MOVE_SPEED=<speed>]]`:
|
||||
Restore the previously saved positions of the dual carriages and their modes,
|
||||
unless "MOVE=0" is specified, in which case only the saved modes will be
|
||||
restored, but not the positions of the carriages. If positions are being
|
||||
restored and "MOVE_SPEED" is specified, then the toolhead moves will be
|
||||
performed with the given speed (in mm/s); otherwise the toolhead move will
|
||||
use the rail homing speed. Note that the carriages restore their positions
|
||||
only over their own axis, which may be necessary to correctly restore COPY
|
||||
and MIRROR mode of the dual carraige.
|
||||
|
||||
### [endstop_phase]
|
||||
|
||||
|
|
|
@ -418,18 +418,34 @@ if necessary.
|
|||
|
||||
### Is dual carriage setup supported with input shapers?
|
||||
|
||||
There is no dedicated support for dual carriages with input shapers, but it does
|
||||
not mean this setup will not work. One should run the tuning twice for each
|
||||
of the carriages, and calculate the ringing frequencies for X and Y axes for
|
||||
each of the carriages independently. Then put the values for carriage 0 into
|
||||
[input_shaper] section, and change the values on the fly when changing
|
||||
carriages, e.g. as a part of some macro:
|
||||
```
|
||||
SET_DUAL_CARRIAGE CARRIAGE=1
|
||||
SET_INPUT_SHAPER SHAPER_FREQ_X=... SHAPER_FREQ_Y=...
|
||||
Yes. In this case, one should measure the resonances twice for each carriage.
|
||||
For example, if the second (dual) carriage is installed on X axis, it is
|
||||
possible to set different input shapers for X axis for the primary and dual
|
||||
carriages. However, the input shaper for Y axis should be the same for both
|
||||
carriages (as ultimately this axis is driven by one or more stepper motors each
|
||||
commanded to perform exactly the same steps). One possibility to configure
|
||||
the input shaper for such setups is to keep `[input_shaper]` section empty and
|
||||
additionally define a `[delayed_gcode]` section in the `printer.cfg` as follows:
|
||||
```
|
||||
[input_shaper]
|
||||
# Intentionally empty
|
||||
|
||||
And similarly when switching back to carriage 0.
|
||||
[delayed_gcode init_shaper]
|
||||
initial_duration: 0.1
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=1
|
||||
SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_shaper> SHAPER_FREQ_X=<dual_carriage_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
|
||||
SET_DUAL_CARRIAGE CARRIAGE=0
|
||||
SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_shaper> SHAPER_FREQ_X=<primary_carriage_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
|
||||
```
|
||||
Note that `SHAPER_TYPE_Y` and `SHAPER_FREQ_Y` should be the same in both
|
||||
commands. It is also possible to put a similar snippet into the start g-code
|
||||
in the slicer, however then the shaper will not be enabled until any print
|
||||
is started.
|
||||
|
||||
Note that the input shaper only needs to be configured once. Subsequent changes
|
||||
of the carriages or their modes via `SET_DUAL_CARRIAGE` command will preserve
|
||||
the configured input shaper parameters.
|
||||
|
||||
### Does input_shaper affect print time?
|
||||
|
||||
|
|
|
@ -503,10 +503,11 @@ The following information is available in the `toolhead` object
|
|||
|
||||
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"
|
||||
on a cartesian, hybrid_corexy or hybrid_corexz robot
|
||||
- `carriage_0`: The mode of the carriage 0. Possible values are:
|
||||
"INACTIVE" and "PRIMARY".
|
||||
- `carriage_1`: The mode of the carriage 1. Possible values are:
|
||||
"INACTIVE", "PRIMARY", "COPY", and "MIRROR".
|
||||
|
||||
## virtual_sdcard
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ SOURCE_FILES = [
|
|||
'pollreactor.c', 'msgblock.c', 'trdispatch.c',
|
||||
'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c',
|
||||
'kin_deltesian.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c',
|
||||
'kin_extruder.c', 'kin_shaper.c',
|
||||
'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c',
|
||||
]
|
||||
DEST_LIB = "c_helper.so"
|
||||
OTHER_FILES = [
|
||||
|
@ -101,7 +101,6 @@ defs_trapq = """
|
|||
|
||||
defs_kin_cartesian = """
|
||||
struct stepper_kinematics *cartesian_stepper_alloc(char axis);
|
||||
struct stepper_kinematics *cartesian_reverse_stepper_alloc(char axis);
|
||||
"""
|
||||
|
||||
defs_kin_corexy = """
|
||||
|
@ -153,6 +152,14 @@ defs_kin_shaper = """
|
|||
struct stepper_kinematics * input_shaper_alloc(void);
|
||||
"""
|
||||
|
||||
defs_kin_idex = """
|
||||
void dual_carriage_set_sk(struct stepper_kinematics *sk
|
||||
, struct stepper_kinematics *orig_sk);
|
||||
int dual_carriage_set_transform(struct stepper_kinematics *sk
|
||||
, char axis, double scale, double offs);
|
||||
struct stepper_kinematics * dual_carriage_alloc(void);
|
||||
"""
|
||||
|
||||
defs_serialqueue = """
|
||||
#define MESSAGE_MAX 64
|
||||
struct pull_queue_message {
|
||||
|
@ -211,7 +218,7 @@ defs_all = [
|
|||
defs_itersolve, defs_trapq, defs_trdispatch,
|
||||
defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta,
|
||||
defs_kin_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch,
|
||||
defs_kin_extruder, defs_kin_shaper,
|
||||
defs_kin_extruder, defs_kin_shaper, defs_kin_idex,
|
||||
]
|
||||
|
||||
# Update filenames to an absolute path
|
||||
|
|
|
@ -49,42 +49,3 @@ cartesian_stepper_alloc(char axis)
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
// Idex dual carriage kinematics
|
||||
//
|
||||
// Copyright (C) 2023 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include <stddef.h> // offsetof
|
||||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // memset
|
||||
#include "compiler.h" // __visible
|
||||
#include "itersolve.h" // struct stepper_kinematics
|
||||
#include "trapq.h" // struct move
|
||||
|
||||
#define DUMMY_T 500.0
|
||||
|
||||
struct dual_carriage_stepper {
|
||||
struct stepper_kinematics sk;
|
||||
struct stepper_kinematics *orig_sk;
|
||||
struct move m;
|
||||
double x_scale, x_offs, y_scale, y_offs;
|
||||
};
|
||||
|
||||
double
|
||||
dual_carriage_calc_position(struct stepper_kinematics *sk, struct move *m
|
||||
, double move_time)
|
||||
{
|
||||
struct dual_carriage_stepper *dc = container_of(
|
||||
sk, struct dual_carriage_stepper, sk);
|
||||
struct coord pos = move_get_coord(m, move_time);
|
||||
dc->m.start_pos.x = pos.x * dc->x_scale + dc->x_offs;
|
||||
dc->m.start_pos.y = pos.y * dc->y_scale + dc->y_offs;
|
||||
dc->m.start_pos.z = pos.z;
|
||||
return dc->orig_sk->calc_position_cb(dc->orig_sk, &dc->m, DUMMY_T);
|
||||
}
|
||||
|
||||
void __visible
|
||||
dual_carriage_set_sk(struct stepper_kinematics *sk
|
||||
, struct stepper_kinematics *orig_sk)
|
||||
{
|
||||
struct dual_carriage_stepper *dc = container_of(
|
||||
sk, struct dual_carriage_stepper, sk);
|
||||
dc->sk.calc_position_cb = dual_carriage_calc_position;
|
||||
dc->sk.active_flags = orig_sk->active_flags;
|
||||
dc->orig_sk = orig_sk;
|
||||
}
|
||||
|
||||
int __visible
|
||||
dual_carriage_set_transform(struct stepper_kinematics *sk, char axis
|
||||
, double scale, double offs)
|
||||
{
|
||||
struct dual_carriage_stepper *dc = container_of(
|
||||
sk, struct dual_carriage_stepper, sk);
|
||||
if (axis == 'x') {
|
||||
dc->x_scale = scale;
|
||||
dc->x_offs = offs;
|
||||
if (!scale)
|
||||
dc->sk.active_flags &= ~AF_X;
|
||||
else if (scale && dc->orig_sk->active_flags & AF_X)
|
||||
dc->sk.active_flags |= AF_X;
|
||||
return 0;
|
||||
}
|
||||
if (axis == 'y') {
|
||||
dc->y_scale = scale;
|
||||
dc->y_offs = offs;
|
||||
if (!scale)
|
||||
dc->sk.active_flags &= ~AF_Y;
|
||||
else if (scale && dc->orig_sk->active_flags & AF_Y)
|
||||
dc->sk.active_flags |= AF_Y;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct stepper_kinematics * __visible
|
||||
dual_carriage_alloc(void)
|
||||
{
|
||||
struct dual_carriage_stepper *dc = malloc(sizeof(*dc));
|
||||
memset(dc, 0, sizeof(*dc));
|
||||
dc->m.move_t = 2. * DUMMY_T;
|
||||
return &dc->sk;
|
||||
}
|
|
@ -204,11 +204,11 @@ input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
|
|||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
struct shaper_pulses *sp = axis == 'x' ? &is->sx : &is->sy;
|
||||
int status = 0;
|
||||
if (is->orig_sk->active_flags & (axis == 'x' ? AF_X : AF_Y))
|
||||
// Ignore input shaper update if the axis is not active
|
||||
if (is->orig_sk->active_flags & (axis == 'x' ? AF_X : AF_Y)) {
|
||||
status = init_shaper(n, a, t, sp);
|
||||
else
|
||||
sp->num_pulses = 0;
|
||||
shaper_note_generation_time(is);
|
||||
shaper_note_generation_time(is);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import stepper
|
||||
from . import idex_modes
|
||||
|
||||
class CartKinematics:
|
||||
def __init__(self, toolhead, config):
|
||||
|
@ -16,6 +17,25 @@ class CartKinematics:
|
|||
for n in 'xyz']
|
||||
for rail, axis in zip(self.rails, 'xyz'):
|
||||
rail.setup_itersolve('cartesian_stepper_alloc', axis.encode())
|
||||
ranges = [r.get_range() for r in self.rails]
|
||||
self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
|
||||
self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
|
||||
self.dc_module = None
|
||||
if config.has_section('dual_carriage'):
|
||||
dc_config = config.getsection('dual_carriage')
|
||||
dc_axis = dc_config.getchoice('axis', {'x': 'x', 'y': 'y'})
|
||||
self.dual_carriage_axis = {'x': 0, 'y': 1}[dc_axis]
|
||||
# setup second dual carriage rail
|
||||
self.rails.append(stepper.LookupMultiRail(dc_config))
|
||||
self.rails[3].setup_itersolve('cartesian_stepper_alloc',
|
||||
dc_axis.encode())
|
||||
dc_rail_0 = idex_modes.DualCarriagesRail(
|
||||
self.rails[0], axis=self.dual_carriage_axis, active=True)
|
||||
dc_rail_1 = idex_modes.DualCarriagesRail(
|
||||
self.rails[3], axis=self.dual_carriage_axis, active=False)
|
||||
self.dc_module = idex_modes.DualCarriages(
|
||||
dc_config, dc_rail_0, dc_rail_1,
|
||||
axis=self.dual_carriage_axis)
|
||||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
|
@ -28,31 +48,18 @@ class CartKinematics:
|
|||
self.max_z_accel = config.getfloat('max_z_accel', max_accel,
|
||||
above=0., maxval=max_accel)
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
ranges = [r.get_range() for r in self.rails]
|
||||
self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
|
||||
self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
|
||||
# Check for dual carriage support
|
||||
if config.has_section('dual_carriage'):
|
||||
dc_config = config.getsection('dual_carriage')
|
||||
dc_axis = dc_config.getchoice('axis', {'x': 'x', 'y': 'y'})
|
||||
self.dual_carriage_axis = {'x': 0, 'y': 1}[dc_axis]
|
||||
dc_rail = stepper.LookupMultiRail(dc_config)
|
||||
dc_rail.setup_itersolve('cartesian_stepper_alloc', dc_axis.encode())
|
||||
for s in dc_rail.get_steppers():
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
self.dual_carriage_rails = [
|
||||
self.rails[self.dual_carriage_axis], dc_rail]
|
||||
self.printer.lookup_object('gcode').register_command(
|
||||
'SET_DUAL_CARRIAGE', self.cmd_SET_DUAL_CARRIAGE,
|
||||
desc=self.cmd_SET_DUAL_CARRIAGE_help)
|
||||
def get_steppers(self):
|
||||
rails = self.rails
|
||||
if self.dual_carriage_axis is not None:
|
||||
dca = self.dual_carriage_axis
|
||||
rails = rails[:dca] + self.dual_carriage_rails + rails[dca+1:]
|
||||
return [s for rail in 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):
|
||||
return [stepper_positions[rail.get_name()] for rail in self.rails]
|
||||
def update_limits(self, i, range):
|
||||
l, h = self.limits[i]
|
||||
# Only update limits if this axis was already homed,
|
||||
# otherwise leave in un-homed state.
|
||||
if l <= h:
|
||||
self.limits[i] = range
|
||||
def override_rail(self, i, rail):
|
||||
self.rails[i] = rail
|
||||
def set_position(self, newpos, homing_axes):
|
||||
for i, rail in enumerate(self.rails):
|
||||
rail.set_position(newpos)
|
||||
|
@ -61,7 +68,7 @@ class CartKinematics:
|
|||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def _home_axis(self, homing_state, axis, rail):
|
||||
def home_axis(self, homing_state, axis, rail):
|
||||
# Determine movement
|
||||
position_min, position_max = rail.get_range()
|
||||
hi = rail.get_homing_info()
|
||||
|
@ -77,16 +84,10 @@ class CartKinematics:
|
|||
def home(self, homing_state):
|
||||
# Each axis is homed independently and in order
|
||||
for axis in homing_state.get_axes():
|
||||
if axis == self.dual_carriage_axis:
|
||||
dc1, dc2 = self.dual_carriage_rails
|
||||
altc = self.rails[axis] == dc2
|
||||
self._activate_carriage(0)
|
||||
self._home_axis(homing_state, axis, dc1)
|
||||
self._activate_carriage(1)
|
||||
self._home_axis(homing_state, axis, dc2)
|
||||
self._activate_carriage(altc)
|
||||
if self.dc_module is not None and axis == self.dual_carriage_axis:
|
||||
self.dc_module.home(homing_state)
|
||||
else:
|
||||
self._home_axis(homing_state, axis, self.rails[axis])
|
||||
self.home_axis(homing_state, axis, self.rails[axis])
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
|
@ -119,24 +120,6 @@ class CartKinematics:
|
|||
'axis_minimum': self.axes_min,
|
||||
'axis_maximum': self.axes_max,
|
||||
}
|
||||
# Dual carriage support
|
||||
def _activate_carriage(self, carriage):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
dc_rail = self.dual_carriage_rails[carriage]
|
||||
dc_axis = self.dual_carriage_axis
|
||||
self.rails[dc_axis].set_trapq(None)
|
||||
dc_rail.set_trapq(toolhead.get_trapq())
|
||||
self.rails[dc_axis] = dc_rail
|
||||
pos = toolhead.get_position()
|
||||
pos[dc_axis] = dc_rail.get_commanded_position()
|
||||
toolhead.set_position(pos)
|
||||
if self.limits[dc_axis][0] <= self.limits[dc_axis][1]:
|
||||
self.limits[dc_axis] = dc_rail.get_range()
|
||||
cmd_SET_DUAL_CARRIAGE_help = "Set which carriage is active"
|
||||
def cmd_SET_DUAL_CARRIAGE(self, gcmd):
|
||||
carriage = gcmd.get_int('CARRIAGE', minval=0, maxval=1)
|
||||
self._activate_carriage(carriage)
|
||||
|
||||
def load_kinematics(toolhead, config):
|
||||
return CartKinematics(toolhead, config)
|
||||
|
|
|
@ -33,17 +33,13 @@ class HybridCoreXYKinematics:
|
|||
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', b'y')
|
||||
self.rails[3].setup_itersolve('corexy_stepper_alloc', b'+')
|
||||
dc_rail_0 = idex_modes.DualCarriagesRail(
|
||||
self.printer, self.rails[0], axis=0, active=True,
|
||||
stepper_alloc_active=('corexy_stepper_alloc', b'-'),
|
||||
stepper_alloc_inactive=('cartesian_reverse_stepper_alloc',b'y'))
|
||||
self.rails[0], axis=0, active=True)
|
||||
dc_rail_1 = idex_modes.DualCarriagesRail(
|
||||
self.printer, self.rails[3], axis=0, active=False,
|
||||
stepper_alloc_active=('corexy_stepper_alloc', b'+'),
|
||||
stepper_alloc_inactive=('cartesian_stepper_alloc', b'y'))
|
||||
self.dc_module = idex_modes.DualCarriages(self.printer,
|
||||
dc_rail_0, dc_rail_1, axis=0)
|
||||
self.rails[3], axis=0, active=False)
|
||||
self.dc_module = idex_modes.DualCarriages(
|
||||
dc_config, dc_rail_0, dc_rail_1, axis=0)
|
||||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
|
@ -60,8 +56,8 @@ class HybridCoreXYKinematics:
|
|||
return [s for rail in self.rails for s in rail.get_steppers()]
|
||||
def calc_position(self, stepper_positions):
|
||||
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
|
||||
if (self.dc_module is not None and 'CARRIAGE_1' == \
|
||||
self.dc_module.get_status()['active_carriage']):
|
||||
if (self.dc_module is not None and 'PRIMARY' == \
|
||||
self.dc_module.get_status()['carriage_1']):
|
||||
return [pos[0] - pos[1], pos[1], pos[2]]
|
||||
else:
|
||||
return [pos[0] + pos[1], pos[1], pos[2]]
|
||||
|
@ -81,7 +77,7 @@ class HybridCoreXYKinematics:
|
|||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def _home_axis(self, homing_state, axis, rail):
|
||||
def home_axis(self, homing_state, axis, rail):
|
||||
position_min, position_max = rail.get_range()
|
||||
hi = rail.get_homing_info()
|
||||
homepos = [None, None, None, None]
|
||||
|
@ -95,14 +91,10 @@ class HybridCoreXYKinematics:
|
|||
homing_state.home_rails([rail], forcepos, homepos)
|
||||
def home(self, homing_state):
|
||||
for axis in homing_state.get_axes():
|
||||
if (self.dc_module is not None and axis == 0):
|
||||
self.dc_module.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()
|
||||
if self.dc_module is not None and axis == 0:
|
||||
self.dc_module.home(homing_state)
|
||||
else:
|
||||
self._home_axis(homing_state, axis, self.rails[axis])
|
||||
self.home_axis(homing_state, axis, self.rails[axis])
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
|
|
|
@ -33,17 +33,13 @@ class HybridCoreXZKinematics:
|
|||
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', b'z')
|
||||
self.rails[3].setup_itersolve('corexz_stepper_alloc', b'+')
|
||||
dc_rail_0 = idex_modes.DualCarriagesRail(
|
||||
self.printer, self.rails[0], axis=0, active=True,
|
||||
stepper_alloc_active=('corexz_stepper_alloc', b'-'),
|
||||
stepper_alloc_inactive=('cartesian_reverse_stepper_alloc',b'z'))
|
||||
self.rails[0], axis=0, active=True)
|
||||
dc_rail_1 = idex_modes.DualCarriagesRail(
|
||||
self.printer, self.rails[3], axis=0, active=False,
|
||||
stepper_alloc_active=('corexz_stepper_alloc', b'+'),
|
||||
stepper_alloc_inactive=('cartesian_stepper_alloc', b'z'))
|
||||
self.dc_module = idex_modes.DualCarriages(self.printer,
|
||||
dc_rail_0, dc_rail_1, axis=0)
|
||||
self.rails[3], axis=0, active=False)
|
||||
self.dc_module = idex_modes.DualCarriages(
|
||||
dc_config, dc_rail_0, dc_rail_1, axis=0)
|
||||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
|
@ -60,8 +56,8 @@ class HybridCoreXZKinematics:
|
|||
return [s for rail in self.rails for s in rail.get_steppers()]
|
||||
def calc_position(self, stepper_positions):
|
||||
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
|
||||
if (self.dc_module is not None and 'CARRIAGE_1' == \
|
||||
self.dc_module.get_status()['active_carriage']):
|
||||
if (self.dc_module is not None and 'PRIMARY' == \
|
||||
self.dc_module.get_status()['carriage_1']):
|
||||
return [pos[0] - pos[2], pos[1], pos[2]]
|
||||
else:
|
||||
return [pos[0] + pos[2], pos[1], pos[2]]
|
||||
|
@ -81,7 +77,7 @@ class HybridCoreXZKinematics:
|
|||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def _home_axis(self, homing_state, axis, rail):
|
||||
def home_axis(self, homing_state, axis, rail):
|
||||
position_min, position_max = rail.get_range()
|
||||
hi = rail.get_homing_info()
|
||||
homepos = [None, None, None, None]
|
||||
|
@ -95,14 +91,10 @@ class HybridCoreXZKinematics:
|
|||
homing_state.home_rails([rail], forcepos, homepos)
|
||||
def home(self, homing_state):
|
||||
for axis in homing_state.get_axes():
|
||||
if (self.dc_module is not None and axis == 0):
|
||||
self.dc_module.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()
|
||||
if self.dc_module is not None and axis == 0:
|
||||
self.dc_module.home(homing_state)
|
||||
else:
|
||||
self._home_axis(homing_state, axis, self.rails[axis])
|
||||
self.home_axis(homing_state, axis, self.rails[axis])
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
|
|
|
@ -1,23 +1,48 @@
|
|||
# Support for duplication and mirroring modes for IDEX printers
|
||||
#
|
||||
# Copyright (C) 2021 Fabrice Gallet <tircown@gmail.com>
|
||||
# Copyright (C) 2023 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import math
|
||||
import chelper
|
||||
|
||||
INACTIVE = 'INACTIVE'
|
||||
PRIMARY = 'PRIMARY'
|
||||
COPY = 'COPY'
|
||||
MIRROR = 'MIRROR'
|
||||
|
||||
class DualCarriages:
|
||||
def __init__(self, printer, rail_0, rail_1, axis):
|
||||
self.printer = printer
|
||||
VALID_MODES = [PRIMARY, COPY, MIRROR]
|
||||
def __init__(self, dc_config, rail_0, rail_1, axis):
|
||||
self.printer = dc_config.get_printer()
|
||||
self.axis = axis
|
||||
self.dc = (rail_0, rail_1)
|
||||
self.saved_state = None
|
||||
self.saved_states = {}
|
||||
safe_dist = dc_config.getfloat('safe_distance', None, minval=0.)
|
||||
if safe_dist is None:
|
||||
dc0_rail = rail_0.get_rail()
|
||||
dc1_rail = rail_1.get_rail()
|
||||
safe_dist = min(abs(dc0_rail.position_min - dc1_rail.position_min),
|
||||
abs(dc0_rail.position_max - dc1_rail.position_max))
|
||||
self.safe_dist = safe_dist
|
||||
self.printer.add_object('dual_carriage', self)
|
||||
self.printer.register_event_handler("klippy:ready", self._handle_ready)
|
||||
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):
|
||||
gcode.register_command(
|
||||
'SAVE_DUAL_CARRIAGE_STATE',
|
||||
self.cmd_SAVE_DUAL_CARRIAGE_STATE,
|
||||
desc=self.cmd_SAVE_DUAL_CARRIAGE_STATE_help)
|
||||
gcode.register_command(
|
||||
'RESTORE_DUAL_CARRIAGE_STATE',
|
||||
self.cmd_RESTORE_DUAL_CARRIAGE_STATE,
|
||||
desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help)
|
||||
def get_rails(self):
|
||||
return self.dc
|
||||
def toggle_active_dc_rail(self, index, override_rail=False):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
pos = toolhead.get_position()
|
||||
|
@ -27,104 +52,170 @@ class DualCarriages:
|
|||
if i != index:
|
||||
if dc.is_active():
|
||||
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())
|
||||
if override_rail:
|
||||
kin.override_rail(3, dc_rail)
|
||||
target_dc = self.dc[index]
|
||||
if target_dc.mode != PRIMARY:
|
||||
newpos = pos[:self.axis] + [target_dc.get_axis_position(pos)] \
|
||||
+ pos[self.axis+1:]
|
||||
target_dc.activate(PRIMARY, newpos, old_position=pos)
|
||||
if override_rail:
|
||||
kin.override_rail(self.axis, target_dc.get_rail())
|
||||
toolhead.set_position(newpos)
|
||||
kin.update_limits(self.axis, target_dc.get_rail().get_range())
|
||||
def home(self, homing_state):
|
||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||
for i, dc_rail in enumerate(self.dc):
|
||||
self.toggle_active_dc_rail(i, override_rail=True)
|
||||
kin.home_axis(homing_state, self.axis, dc_rail.get_rail())
|
||||
# Restore the original rails ordering
|
||||
self.toggle_active_dc_rail(0, override_rail=True)
|
||||
def get_status(self, eventtime=None):
|
||||
dc0, dc1 = self.dc
|
||||
if (dc0.is_active() is True):
|
||||
return { 'mode': 'FULL_CONTROL', 'active_carriage': 'CARRIAGE_0' }
|
||||
return {('carriage_%d' % (i,)) : dc.mode
|
||||
for (i, dc) in enumerate(self.dc)}
|
||||
def get_kin_range(self, toolhead, mode):
|
||||
pos = toolhead.get_position()
|
||||
axes_pos = [dc.get_axis_position(pos) for dc in self.dc]
|
||||
dc0_rail = self.dc[0].get_rail()
|
||||
dc1_rail = self.dc[1].get_rail()
|
||||
range_min = dc0_rail.position_min
|
||||
range_max = dc0_rail.position_max
|
||||
safe_dist = self.safe_dist
|
||||
|
||||
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:
|
||||
if dc0_rail.get_homing_info().positive_dir:
|
||||
range_min = max(range_min,
|
||||
0.5 * (sum(axes_pos) + safe_dist))
|
||||
range_max = min(range_max,
|
||||
sum(axes_pos) - dc1_rail.position_min)
|
||||
else:
|
||||
range_max = min(range_max,
|
||||
0.5 * (sum(axes_pos) - safe_dist))
|
||||
range_min = max(range_min,
|
||||
sum(axes_pos) - dc1_rail.position_max)
|
||||
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')
|
||||
# mode == PRIMARY
|
||||
active_idx = 1 if self.dc[1].is_active() else 0
|
||||
inactive_idx = 1 - active_idx
|
||||
if active_idx:
|
||||
range_min = dc1_rail.position_min
|
||||
range_max = dc1_rail.position_max
|
||||
if self.dc[active_idx].get_rail().get_homing_info().positive_dir:
|
||||
range_min = max(range_min, axes_pos[inactive_idx] + safe_dist)
|
||||
else:
|
||||
range_max = min(range_max, axes_pos[inactive_idx] - safe_dist)
|
||||
return (range_min, range_max)
|
||||
def activate_dc_mode(self, index, mode):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
kin = toolhead.get_kinematics()
|
||||
if mode == INACTIVE:
|
||||
self.dc[index].inactivate(toolhead.get_position())
|
||||
elif mode == PRIMARY:
|
||||
self.toggle_active_dc_rail(index)
|
||||
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"
|
||||
self.toggle_active_dc_rail(0)
|
||||
self.dc[index].activate(mode, toolhead.get_position())
|
||||
kin.update_limits(self.axis, self.get_kin_range(toolhead, mode))
|
||||
def _handle_ready(self):
|
||||
# Apply the transform later during Klipper initialization to make sure
|
||||
# that input shaping can pick up the correct stepper kinematic flags.
|
||||
for dc in self.dc:
|
||||
dc.apply_transform()
|
||||
cmd_SET_DUAL_CARRIAGE_help = "Configure the dual carriages mode"
|
||||
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)
|
||||
mode = gcmd.get('MODE', PRIMARY).upper()
|
||||
if mode not in self.VALID_MODES:
|
||||
raise gcmd.error("Invalid mode=%s specified" % (mode,))
|
||||
if mode in [COPY, MIRROR]:
|
||||
if index == 0:
|
||||
raise gcmd.error(
|
||||
"Mode=%s is not supported for carriage=0" % (mode,))
|
||||
curtime = self.printer.get_reactor().monotonic()
|
||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||
axis = 'xyz'[self.axis]
|
||||
if axis not in kin.get_status(curtime)['homed_axes']:
|
||||
raise gcmd.error(
|
||||
"Axis %s must be homed prior to enabling mode=%s" %
|
||||
(axis, mode))
|
||||
self.activate_dc_mode(index, mode)
|
||||
cmd_SAVE_DUAL_CARRIAGE_STATE_help = \
|
||||
"Save dual carriages modes and positions"
|
||||
def cmd_SAVE_DUAL_CARRIAGE_STATE(self, gcmd):
|
||||
state_name = gcmd.get('NAME', 'default')
|
||||
pos = self.printer.lookup_object('toolhead').get_position()
|
||||
self.saved_states[state_name] = {
|
||||
'carriage_modes': [dc.mode for dc in self.dc],
|
||||
'axes_positions': [dc.get_axis_position(pos) for dc in self.dc],
|
||||
}
|
||||
cmd_RESTORE_DUAL_CARRIAGE_STATE_help = \
|
||||
"Restore dual carriages modes and positions"
|
||||
def cmd_RESTORE_DUAL_CARRIAGE_STATE(self, gcmd):
|
||||
state_name = gcmd.get('NAME', 'default')
|
||||
saved_state = self.saved_states.get(state_name)
|
||||
if saved_state is None:
|
||||
raise gcmd.error("Unknown DUAL_CARRIAGE state: %s" % (state_name,))
|
||||
move_speed = gcmd.get('MOVE_SPEED', 0., above=0.)
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
pos = toolhead.get_position()
|
||||
if gcmd.get_int('MOVE', 1):
|
||||
for i, dc in enumerate(self.dc):
|
||||
self.toggle_active_dc_rail(i)
|
||||
saved_pos = saved_state['axes_positions'][i]
|
||||
toolhead.manual_move(
|
||||
pos[:self.axis] + [saved_pos] + pos[self.axis+1:],
|
||||
move_speed or dc.get_rail().homing_speed)
|
||||
for i, dc in enumerate(self.dc):
|
||||
saved_mode = saved_state['carriage_modes'][i]
|
||||
self.activate_dc_mode(i, saved_mode)
|
||||
|
||||
class DualCarriagesRail:
|
||||
ACTIVE=1
|
||||
INACTIVE=2
|
||||
def __init__(self, printer, rail, axis, active, stepper_alloc_active,
|
||||
stepper_alloc_inactive=None):
|
||||
self.printer = printer
|
||||
ENC_AXES = [b'x', b'y']
|
||||
def __init__(self, rail, axis, active):
|
||||
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
|
||||
self.stepper_active_sk = {}
|
||||
self.stepper_inactive_sk = {}
|
||||
for s in rail.get_steppers():
|
||||
self._save_sk(self.status, s, s.get_stepper_kinematics())
|
||||
def _alloc_sk(self, alloc_func, *params):
|
||||
self.mode = (INACTIVE, PRIMARY)[active]
|
||||
self.offset = 0.
|
||||
self.scale = 1. if active else 0.
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
return ffi_main.gc(getattr(ffi_lib, alloc_func)(*params), ffi_lib.free)
|
||||
def _get_sk(self, status, stepper):
|
||||
sk = None
|
||||
if status == self.ACTIVE:
|
||||
sk = self.stepper_active_sk.get(stepper, None)
|
||||
if sk is None and self.stepper_alloc_active:
|
||||
sk = self._alloc_sk(*self.stepper_alloc_active)
|
||||
self._save_sk(status, stepper, sk)
|
||||
elif status == self.INACTIVE:
|
||||
sk = self.stepper_inactive_sk.get(stepper, None)
|
||||
if sk is None and self.stepper_alloc_inactive:
|
||||
sk = self._alloc_sk(*self.stepper_alloc_inactive)
|
||||
self._save_sk(status, stepper, sk)
|
||||
return sk
|
||||
def _save_sk(self, status, stepper, sk):
|
||||
if status == self.ACTIVE:
|
||||
self.stepper_active_sk[stepper] = sk
|
||||
elif status == self.INACTIVE:
|
||||
self.stepper_inactive_sk[stepper] = sk
|
||||
def _update_stepper_alloc(self, position, active=True):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
self.axis_position = position[self.axis]
|
||||
self.rail.set_trapq(None)
|
||||
old_status = self.status
|
||||
self.status = (self.INACTIVE, self.ACTIVE)[active]
|
||||
for s in self.rail.get_steppers():
|
||||
sk = self._get_sk(self.status, s)
|
||||
if sk is None:
|
||||
return
|
||||
old_sk = s.set_stepper_kinematics(sk)
|
||||
self._save_sk(old_status, s, old_sk)
|
||||
self.rail.set_position(position)
|
||||
self.rail.set_trapq(toolhead.get_trapq())
|
||||
self.dc_stepper_kinematics = []
|
||||
self.orig_stepper_kinematics = []
|
||||
for s in rail.get_steppers():
|
||||
sk = ffi_main.gc(ffi_lib.dual_carriage_alloc(), ffi_lib.free)
|
||||
orig_sk = s.get_stepper_kinematics()
|
||||
ffi_lib.dual_carriage_set_sk(sk, orig_sk)
|
||||
# Set the default transform for the other axis
|
||||
ffi_lib.dual_carriage_set_transform(
|
||||
sk, self.ENC_AXES[1 - axis], 1., 0.)
|
||||
self.dc_stepper_kinematics.append(sk)
|
||||
self.orig_stepper_kinematics.append(orig_sk)
|
||||
s.set_stepper_kinematics(sk)
|
||||
def get_rail(self):
|
||||
return self.rail
|
||||
def is_active(self):
|
||||
return self.status == self.ACTIVE
|
||||
def activate(self, position):
|
||||
self._update_stepper_alloc(position, active=True)
|
||||
return self.mode != INACTIVE
|
||||
def get_axis_position(self, position):
|
||||
return position[self.axis] * self.scale + self.offset
|
||||
def apply_transform(self):
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
for sk in self.dc_stepper_kinematics:
|
||||
ffi_lib.dual_carriage_set_transform(
|
||||
sk, self.ENC_AXES[self.axis], self.scale, self.offset)
|
||||
def activate(self, mode, position, old_position=None):
|
||||
old_axis_position = self.get_axis_position(old_position or position)
|
||||
self.scale = -1. if mode == MIRROR else 1.
|
||||
self.offset = old_axis_position - position[self.axis] * self.scale
|
||||
self.apply_transform()
|
||||
self.mode = mode
|
||||
def inactivate(self, position):
|
||||
self._update_stepper_alloc(position, active=False)
|
||||
self.offset = self.get_axis_position(position)
|
||||
self.scale = 0.
|
||||
self.apply_transform()
|
||||
self.mode = INACTIVE
|
||||
|
|
Loading…
Reference in New Issue