mirror of
https://github.com/Klipper3d/klipper.git
synced 2025-08-19 08:02:11 -06:00
kinematics: Generic Cartesian kinematics implementation (#6815)
* tests: Added a regression test for generic_cartesian kinematics * kinematics: An intial implementation of generic_cartesian kinematics * generic_cartesian: Refactored kinematics configuration API * generic_cartesian: Use stepper instead of kinematic_stepper in configs * generic_cartesian: Added SET_STEPPER_KINEMATICS command * generic_cartesian: Fixed parsing of section names * docs: Generic Caretsian kinematics documentation and config samples * generic_cartesian: Implemented multi-mcu homing validation * generic_cartesian: Fixed typos in docs, minor fixes * generic_cartesian: Renamed `kinematics` option to `carriages` * generic_cartesian: Moved kinematic_stepper.py file * idex_modes: Internal refactoring of handling dual carriages * stepper: Refactored the code to not store a reference to config object * config: Updated example-generic-cartesian config * generic_cartesian: Restricted SET_STEPPER_CARRIAGES and exported status * idex_modes: Fixed handling stepper kinematics with input shaper enabled * config: Updated configs and tests for SET_DUAL_CARRIAGE new params * generic_cartesian: Avoid inheritance in the added classes Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
This commit is contained in:
parent
1cc6398074
commit
cc6736c3e3
27 changed files with 1855 additions and 199 deletions
138
config/example-generic-caretesian.cfg
Normal file
138
config/example-generic-caretesian.cfg
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
# This file is an example config file for cartesian style printers.
|
||||||
|
# One may copy and edit this file to configure a new printer with
|
||||||
|
# a generic cartesian kinematics.
|
||||||
|
|
||||||
|
# DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
|
||||||
|
# FIRST. Incorrectly configured parameters may cause damage.
|
||||||
|
|
||||||
|
# See docs/Config_Reference.md for a description of parameters.
|
||||||
|
|
||||||
|
[carriage x]
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PE5
|
||||||
|
|
||||||
|
[carriage y]
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PJ1
|
||||||
|
|
||||||
|
[extra_carriage y1]
|
||||||
|
primary_carriage: y
|
||||||
|
endstop_pin: ^PB6
|
||||||
|
|
||||||
|
[carriage z]
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 100
|
||||||
|
endstop_pin: ^PD3
|
||||||
|
|
||||||
|
[dual_carriage u]
|
||||||
|
primary_carriage: x
|
||||||
|
position_endstop: 300
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PE4
|
||||||
|
|
||||||
|
[stepper my_stepper_x]
|
||||||
|
carriages: x+y
|
||||||
|
step_pin: PF0
|
||||||
|
dir_pin: PF1
|
||||||
|
enable_pin: !PD7
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper my_stepper_u]
|
||||||
|
carriages: u-y1
|
||||||
|
step_pin: PH1
|
||||||
|
dir_pin: PH0
|
||||||
|
enable_pin: !PA1
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper my_stepper_y0]
|
||||||
|
carriages: y
|
||||||
|
step_pin: PF6
|
||||||
|
dir_pin: !PF7
|
||||||
|
enable_pin: !PF2
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper my_stepper_y1]
|
||||||
|
carriages: y1
|
||||||
|
step_pin: PE3
|
||||||
|
dir_pin: !PH6
|
||||||
|
enable_pin: !PG5
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper my_stepper_z0]
|
||||||
|
carriages: z
|
||||||
|
step_pin: PL3
|
||||||
|
dir_pin: PL1
|
||||||
|
enable_pin: !PK0
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8
|
||||||
|
|
||||||
|
[stepper my_stepper_z1]
|
||||||
|
carriages: z
|
||||||
|
step_pin: PG1
|
||||||
|
dir_pin: PG0
|
||||||
|
enable_pin: !PH3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PA4
|
||||||
|
dir_pin: PA6
|
||||||
|
enable_pin: !PA2
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.5
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB4
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK5
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[extruder1]
|
||||||
|
step_pin: PC1
|
||||||
|
dir_pin: PC3
|
||||||
|
enable_pin: !PC7
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.5
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK7
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PH5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK6
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 110
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyACM0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: generic_cartesian
|
||||||
|
max_velocity: 500
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 20
|
||||||
|
max_z_accel: 100
|
177
config/sample-corexyuv.cfg
Normal file
177
config/sample-corexyuv.cfg
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
# This file contains a configuration snippet for a CoreXYUV
|
||||||
|
# printer with an independent dual extruder moving over X and Y axes.
|
||||||
|
|
||||||
|
# See docs/Config_Reference.md for a description of parameters.
|
||||||
|
|
||||||
|
[carriage x]
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PE5
|
||||||
|
|
||||||
|
[carriage y]
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PJ1
|
||||||
|
|
||||||
|
[dual_carriage u]
|
||||||
|
primary_carriage: x
|
||||||
|
safe_distance: 70
|
||||||
|
position_endstop: 300
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PE4
|
||||||
|
|
||||||
|
[dual_carriage v]
|
||||||
|
primary_carriage: y
|
||||||
|
safe_distance: 50
|
||||||
|
position_endstop: 200
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PD4
|
||||||
|
|
||||||
|
[stepper a]
|
||||||
|
carriages: x+y
|
||||||
|
step_pin: PF0
|
||||||
|
dir_pin: PF1
|
||||||
|
enable_pin: !PD7
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper b]
|
||||||
|
carriages: u-v
|
||||||
|
step_pin: PH1
|
||||||
|
dir_pin: PH0
|
||||||
|
enable_pin: !PA1
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper c]
|
||||||
|
carriages: x-y
|
||||||
|
step_pin: PF6
|
||||||
|
dir_pin: !PF7
|
||||||
|
enable_pin: !PF2
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper d]
|
||||||
|
carriages: u+v
|
||||||
|
step_pin: PE3
|
||||||
|
dir_pin: !PH6
|
||||||
|
enable_pin: !PG5
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PA4
|
||||||
|
dir_pin: PA6
|
||||||
|
enable_pin: !PA2
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.5
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB4
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK5
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[gcode_macro PARK_extruder]
|
||||||
|
gcode:
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=y
|
||||||
|
G90
|
||||||
|
G1 X0 Y0
|
||||||
|
|
||||||
|
[gcode_macro T0]
|
||||||
|
gcode:
|
||||||
|
PARK_{printer.toolhead.extruder}
|
||||||
|
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=y
|
||||||
|
|
||||||
|
[extruder1]
|
||||||
|
step_pin: PC1
|
||||||
|
dir_pin: PC3
|
||||||
|
enable_pin: !PC7
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.5
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK7
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[gcode_macro PARK_extruder1]
|
||||||
|
gcode:
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=v
|
||||||
|
G90
|
||||||
|
G1 X300 Y200
|
||||||
|
|
||||||
|
[gcode_macro T1]
|
||||||
|
gcode:
|
||||||
|
PARK_{printer.toolhead.extruder}
|
||||||
|
ACTIVATE_EXTRUDER EXTRUDER=extruder1
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=v
|
||||||
|
|
||||||
|
# A helper script to activate copy mode
|
||||||
|
[gcode_macro ACTIVATE_COPY_MODE]
|
||||||
|
gcode:
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
|
||||||
|
G1 X0 Y0
|
||||||
|
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u MODE=PRIMARY
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=v MODE=PRIMARY
|
||||||
|
G1 X150 Y100
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u MODE=COPY
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=v 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=x MODE=PRIMARY
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
|
||||||
|
G1 X0 Y0
|
||||||
|
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u MODE=PRIMARY
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=v MODE=PRIMARY
|
||||||
|
G1 X300 Y100
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u MODE=MIRROR
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=v MODE=COPY
|
||||||
|
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: generic_cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
## 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=u
|
||||||
|
# SET_DUAL_CARRIAGE CARRIAGE=v
|
||||||
|
# SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_x_shaper> SHAPER_FREQ_X=<dual_carriage_x_freq> SHAPER_TYPE_Y=<dual_carriage_y_shaper> SHAPER_FREQ_Y=<dual_carriage_y_freq>
|
||||||
|
# SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
|
||||||
|
# SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
|
||||||
|
# SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_x_shaper> SHAPER_FREQ_X=<primary_carriage_x_freq> SHAPER_TYPE_Y=<primary_carriage_y_shaper> SHAPER_FREQ_Y=<primary_carriage_y_freq>
|
|
@ -84,8 +84,9 @@ The printer section controls high level printer settings.
|
||||||
[printer]
|
[printer]
|
||||||
kinematics:
|
kinematics:
|
||||||
# The type of printer in use. This option may be one of: cartesian,
|
# The type of printer in use. This option may be one of: cartesian,
|
||||||
# corexy, corexz, hybrid_corexy, hybrid_corexz, rotary_delta, delta,
|
# corexy, corexz, hybrid_corexy, hybrid_corexz, generic_cartesian,
|
||||||
# deltesian, polar, winch, or none. This parameter must be specified.
|
# rotary_delta, delta, deltesian, polar, winch, or none.
|
||||||
|
# This parameter must be specified.
|
||||||
max_velocity:
|
max_velocity:
|
||||||
# Maximum velocity (in mm/s) of the toolhead (relative to the
|
# Maximum velocity (in mm/s) of the toolhead (relative to the
|
||||||
# print). This value may be changed at runtime using the
|
# print). This value may be changed at runtime using the
|
||||||
|
@ -712,6 +713,172 @@ anchor_z:
|
||||||
# These parameters must be provided.
|
# These parameters must be provided.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Generic Cartesian Kinematics
|
||||||
|
|
||||||
|
See [example-generic-cartesian.cfg](../config/example-generic-caretesian.cfg)
|
||||||
|
for an example generic Cartesian kinematics config file.
|
||||||
|
|
||||||
|
This printer kinematic class allows a user to define in a pretty flexible
|
||||||
|
manner an arbitrary Cartesian-style kinematics. In principle, the regular
|
||||||
|
cartesian, corexy, hybrid_corexy can be defined this way too. However,
|
||||||
|
more importantly, various otherwise unsupported kinematics such as
|
||||||
|
inverted hybrid_corexy or corexyuv can be defined using this kinematic.
|
||||||
|
|
||||||
|
Notably, the definition of a generic Cartesian kinematic deviates
|
||||||
|
significantly from the other kinematic types. It follows the following
|
||||||
|
convention: a user defines a set of carriages with certain range of motion
|
||||||
|
that can move independently from each other (they should move over the
|
||||||
|
Cartesian axes X, Y, and Z, hence the name of the kinematic) and
|
||||||
|
corresponding endstops that allow the firmware to determine the position
|
||||||
|
of carriages during homing, as well as a set of steppers that move those
|
||||||
|
carriages. The `[printer]` section must specify the kinematic and
|
||||||
|
other printer-level settings same as the regular Cartesian kinematic:
|
||||||
|
```
|
||||||
|
[printer]
|
||||||
|
kinematics: generic_cartesian
|
||||||
|
max_velocity:
|
||||||
|
max_accel:
|
||||||
|
#minimum_cruise_ratio:
|
||||||
|
#square_corner_velocity:
|
||||||
|
#max_accel_to_decel:
|
||||||
|
#max_z_velocity:
|
||||||
|
#max_z_accel:
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Then a user must define the following three carriages: `[carriage x]`,
|
||||||
|
`[carriage y]`, and `[carriage z]`, e.g.
|
||||||
|
```
|
||||||
|
[carriage x]
|
||||||
|
endstop_pin:
|
||||||
|
# Endstop switch detection pin. If this endstop pin is on a
|
||||||
|
# different mcu than the stepper motor(s) moving this carriage,
|
||||||
|
# then it enables "multi-mcu homing". This parameter must be provided.
|
||||||
|
#position_min: 0
|
||||||
|
# Minimum valid distance (in mm) the user may command the carriage to
|
||||||
|
# move to. The default is 0mm.
|
||||||
|
position_endstop:
|
||||||
|
# Location of the endstop (in mm). This parameter must be provided.
|
||||||
|
position_max:
|
||||||
|
# Maximum valid distance (in mm) the user may command the stepper to
|
||||||
|
# move to. This parameter must be provided.
|
||||||
|
#homing_speed: 5.0
|
||||||
|
# Maximum velocity (in mm/s) of the carriage when homing. The default
|
||||||
|
# is 5mm/s.
|
||||||
|
#homing_retract_dist: 5.0
|
||||||
|
# Distance to backoff (in mm) before homing a second time during
|
||||||
|
# homing. Set this to zero to disable the second home. The default
|
||||||
|
# is 5mm.
|
||||||
|
#homing_retract_speed:
|
||||||
|
# Speed to use on the retract move after homing in case this should
|
||||||
|
# be different from the homing speed, which is the default for this
|
||||||
|
# parameter
|
||||||
|
#second_homing_speed:
|
||||||
|
# Velocity (in mm/s) of the carriage when performing the second home.
|
||||||
|
# The default is homing_speed/2.
|
||||||
|
#homing_positive_dir:
|
||||||
|
# If true, homing will cause the carriage to move in a positive
|
||||||
|
# direction (away from zero); if false, home towards zero. It is
|
||||||
|
# better to use the default than to specify this parameter. The
|
||||||
|
# default is true if position_endstop is near position_max and false
|
||||||
|
# if near position_min.
|
||||||
|
```
|
||||||
|
|
||||||
|
Afterwards, a user specifies the stepper motors that move these carriages,
|
||||||
|
for instance
|
||||||
|
```
|
||||||
|
[stepper my_stepper]
|
||||||
|
carriages:
|
||||||
|
# A string describing the carriages the stepper moves. All defined
|
||||||
|
# carriages can be specified here, as well as their linear combinations,
|
||||||
|
# e.g. x, x+y, y-0.5*z, x-z, etc. This parameter must be provided.
|
||||||
|
step_pin:
|
||||||
|
dir_pin:
|
||||||
|
enable_pin:
|
||||||
|
rotation_distance:
|
||||||
|
microsteps:
|
||||||
|
#full_steps_per_rotation: 200
|
||||||
|
#gear_ratio:
|
||||||
|
#step_pulse_duration:
|
||||||
|
```
|
||||||
|
See [stepper](#stepper) section for more information on the regular
|
||||||
|
stepper parameters. The `carriages` parameter defines how the stepper
|
||||||
|
affects the motion of the carriages. For example, `x+y` indicates that
|
||||||
|
the motion of the stepper in the positive direction by the distance `d`
|
||||||
|
moves the carriages `x` and `y` by the same distance `d` in the positive
|
||||||
|
direction, while `x-0.5*y` means the motion of the stepper in the positive
|
||||||
|
direction by the distance `d` moves the carriage `x` by the distance `d`
|
||||||
|
in the positive direction, but the carriage `y` will travel distance `d/2`
|
||||||
|
in the negative direction.
|
||||||
|
|
||||||
|
More than a single stepper motor can be defined to drive the same axis
|
||||||
|
or belt. For example, on a CoreXY AWD setups two motors driving the same
|
||||||
|
belt can be defined as
|
||||||
|
```
|
||||||
|
[carriage x]
|
||||||
|
endstop_pin: ...
|
||||||
|
...
|
||||||
|
|
||||||
|
[carriage y]
|
||||||
|
endstop_pin: ...
|
||||||
|
...
|
||||||
|
|
||||||
|
[stepper a0]
|
||||||
|
carriages: x-y
|
||||||
|
step_pin: ...
|
||||||
|
dir_pin: ...
|
||||||
|
enable_pin: ...
|
||||||
|
rotation_distance: ...
|
||||||
|
...
|
||||||
|
|
||||||
|
[stepper a1]
|
||||||
|
carriages: x-y
|
||||||
|
step_pin: ...
|
||||||
|
dir_pin: ...
|
||||||
|
enable_pin: ...
|
||||||
|
rotation_distance: ...
|
||||||
|
...
|
||||||
|
```
|
||||||
|
with `a0` and `a1` steppers having their own control pins, but
|
||||||
|
sharing the same `carriages` and corresponding endstops.
|
||||||
|
|
||||||
|
There are situations when a user wants to have more than one endstop
|
||||||
|
per axis. Examples of such configurations include Y axis driven by
|
||||||
|
two independent stepper motors with belts attached to both ends of the
|
||||||
|
X beam, with effectively two carriages on Y axis each having an
|
||||||
|
independent endstop, and multi-stepper Z axis with each stepper having
|
||||||
|
its own endstop (not to be confused with the configurations with
|
||||||
|
multiple Z motors but only a single endstop). These configurations
|
||||||
|
can be declared by specifying additional carriage(s) with their endstops:
|
||||||
|
|
||||||
|
```
|
||||||
|
[extra_carriage my_carriage]
|
||||||
|
primary_carriage:
|
||||||
|
# The name of the primary carriage this carriage corresponds to.
|
||||||
|
# It also effectively defines the axis the carriage moves over.
|
||||||
|
# This parameter must be provided.
|
||||||
|
endstop_pin:
|
||||||
|
# Endstop switch detection pin. This parameter must be provided.
|
||||||
|
```
|
||||||
|
|
||||||
|
and the corresponding stepper motors, for example:
|
||||||
|
```
|
||||||
|
[extra_carriage y1]
|
||||||
|
primary_carriage: y
|
||||||
|
endstop_pin: ...
|
||||||
|
|
||||||
|
[stepper sy1]
|
||||||
|
carriages: y1
|
||||||
|
...
|
||||||
|
```
|
||||||
|
Notably, an `[extra_carriage]` does not define parameters such as
|
||||||
|
`position_min`, `position_max`, and `position_endstop`, but instead
|
||||||
|
inherits them from the specified `primary_carriage`, thus sharing
|
||||||
|
the same range of motion with the primary carriage.
|
||||||
|
|
||||||
|
For the references on how to configure IDEX setups, see the
|
||||||
|
[dual carriage](#dual-carriage) section.
|
||||||
|
|
||||||
### None Kinematics
|
### None Kinematics
|
||||||
|
|
||||||
It is possible to define a special "none" kinematics to disable
|
It is possible to define a special "none" kinematics to disable
|
||||||
|
@ -2207,8 +2374,8 @@ for an example configuration.
|
||||||
|
|
||||||
### [dual_carriage]
|
### [dual_carriage]
|
||||||
|
|
||||||
Support for cartesian and hybrid_corexy/z printers with dual carriages
|
Support for cartesian, generic_cartesian and hybrid_corexy/z printers with
|
||||||
on a single axis. The carriage mode can be set via the
|
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 extended g-code command. For example,
|
||||||
"SET_DUAL_CARRIAGE CARRIAGE=1" command will activate the carriage defined
|
"SET_DUAL_CARRIAGE CARRIAGE=1" command will activate the carriage defined
|
||||||
in this section (CARRIAGE=0 will return activation to the primary carriage).
|
in this section (CARRIAGE=0 will return activation to the primary carriage).
|
||||||
|
@ -2235,7 +2402,7 @@ typically be achieved with
|
||||||
or a similar command.
|
or a similar command.
|
||||||
|
|
||||||
See [sample-idex.cfg](../config/sample-idex.cfg) for an example
|
See [sample-idex.cfg](../config/sample-idex.cfg) for an example
|
||||||
configuration.
|
configuration with a regular Cartesian kinematic.
|
||||||
|
|
||||||
```
|
```
|
||||||
[dual_carriage]
|
[dual_carriage]
|
||||||
|
@ -2249,7 +2416,7 @@ axis:
|
||||||
# error. If safe_distance is not provided, it will be inferred from
|
# 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
|
# 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
|
# to 0 (or safe_distance is unset and position_min and position_max are
|
||||||
# identical for the primary and dual carraiges), the carriages proximity
|
# identical for the primary and dual carriages), the carriages proximity
|
||||||
# checks will be disabled.
|
# checks will be disabled.
|
||||||
#step_pin:
|
#step_pin:
|
||||||
#dir_pin:
|
#dir_pin:
|
||||||
|
@ -2263,6 +2430,62 @@ axis:
|
||||||
# See the "stepper" section for the definition of the above parameters.
|
# See the "stepper" section for the definition of the above parameters.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For an example of dual carriage configuration with `generic_cartesian`
|
||||||
|
kinematic, see the following configuration
|
||||||
|
[sample](../config/example-generic-caretesian.cfg).
|
||||||
|
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. Valid choices are x, y, z.
|
||||||
|
# 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 carriages), the carriages proximity
|
||||||
|
# checks will be disabled.
|
||||||
|
endstop_pin:
|
||||||
|
#position_min:
|
||||||
|
position_endstop:
|
||||||
|
position_max:
|
||||||
|
#homing_speed:
|
||||||
|
#homing_retract_dist:
|
||||||
|
#homing_retract_speed:
|
||||||
|
#second_homing_speed:
|
||||||
|
#homing_positive_dir:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
Refer to [generic cartesian](#generic-cartesian) section for more information
|
||||||
|
on the regular `carriage` parameters.
|
||||||
|
|
||||||
|
Then a user must define one or more stepper motors moving the dual carriage
|
||||||
|
(and other carriages as appropriate), for instance
|
||||||
|
```
|
||||||
|
[carriage x]
|
||||||
|
...
|
||||||
|
|
||||||
|
[carriage y]
|
||||||
|
...
|
||||||
|
|
||||||
|
[dual_carriage u]
|
||||||
|
primary_carriage: x
|
||||||
|
...
|
||||||
|
|
||||||
|
[stepper dc_stepper]
|
||||||
|
carriages: u-y
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
It is worth noting that `generic_cartesian` kinematic can support two
|
||||||
|
dual carriages for X and Y axes. For reference, see for instance a
|
||||||
|
[sample](../config/sample-corexyuv.cfg) of CoreXYUV configuration.
|
||||||
|
|
||||||
### [extruder_stepper]
|
### [extruder_stepper]
|
||||||
|
|
||||||
Support for additional steppers synchronized to the movement of an
|
Support for additional steppers synchronized to the movement of an
|
||||||
|
|
|
@ -341,15 +341,18 @@ The following command is available when the
|
||||||
enabled.
|
enabled.
|
||||||
|
|
||||||
#### SET_DUAL_CARRIAGE
|
#### SET_DUAL_CARRIAGE
|
||||||
`SET_DUAL_CARRIAGE CARRIAGE=[0|1] [MODE=[PRIMARY|COPY|MIRROR]]`:
|
`SET_DUAL_CARRIAGE CARRIAGE=<carriage> [MODE=[PRIMARY|COPY|MIRROR]]`:
|
||||||
This command will change the mode of the specified carriage.
|
This command will change the mode of the specified carriage.
|
||||||
If no `MODE` is provided it defaults to `PRIMARY`. Setting the mode
|
If no `MODE` is provided it defaults to `PRIMARY`. `<carriage>` must
|
||||||
to `PRIMARY` deactivates the other carriage and makes the specified
|
reference a defined primary or dual carriage for `generic_cartesian`
|
||||||
carriage execute subsequent G-Code commands as-is. `COPY` and `MIRROR`
|
kinematics or be 0 (for primary carriage) or 1 (for dual carriage)
|
||||||
modes are supported only for `CARRIAGE=1`. When set to either of these
|
for all other kinematics supporting IDEX. Setting the mode to `PRIMARY`
|
||||||
modes, carriage 1 will then track the subsequent moves of the carriage 0
|
deactivates the other carriage and makes the specified carriage execute
|
||||||
and either copy relative movements of it (in `COPY` mode) or execute them
|
subsequent G-Code commands as-is. `COPY` and `MIRROR` modes are supported
|
||||||
in the opposite (mirror) direction (in `MIRROR` mode).
|
only for dual carriages. When set to either of these modes, dual 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).
|
||||||
|
|
||||||
#### SAVE_DUAL_CARRIAGE_STATE
|
#### SAVE_DUAL_CARRIAGE_STATE
|
||||||
`SAVE_DUAL_CARRIAGE_STATE [NAME=<state_name>]`: Save the current positions
|
`SAVE_DUAL_CARRIAGE_STATE [NAME=<state_name>]`: Save the current positions
|
||||||
|
@ -715,6 +718,46 @@ is specified then the toolhead move will be performed with the given
|
||||||
speed (in mm/s); otherwise the toolhead move will use the restored
|
speed (in mm/s); otherwise the toolhead move will use the restored
|
||||||
g-code speed.
|
g-code speed.
|
||||||
|
|
||||||
|
### [generic_cartesian]
|
||||||
|
The commands in this section become automatically available when
|
||||||
|
`kinematics: generic_cartesian` is specified as the printer kinematics.
|
||||||
|
|
||||||
|
#### SET_STEPPER_CARRIAGES
|
||||||
|
`SET_STEPPER_CARRIAGES STEPPER=<stepper_name> CARRIAGES=<carriages>
|
||||||
|
[DISABLE_CHECKS=[0|1]]`: Set or update the stepper carriages.
|
||||||
|
`<stepper_name>` must reference an existing stepper defined in `printer.cfg`,
|
||||||
|
and `<carriages>` describes the carriages the stepper moves. See
|
||||||
|
[Generic Cartesian Kinematics](Config_Reference.md#generic-cartesian-kinematics)
|
||||||
|
for a more detailed overview of the `carriages` parameter in the
|
||||||
|
stepper configuration section. Note that it is only possible
|
||||||
|
to change the coefficients or signs of the carriages with this
|
||||||
|
command, but a user cannot add or remove the carriages that the stepper
|
||||||
|
controls.
|
||||||
|
|
||||||
|
`SET_STEPPER_CARRIAGES` is an advanced tool, and the user is advised
|
||||||
|
to exercise an extreme caution using it, since specifying incorrect
|
||||||
|
configuration may physically damage the printer.
|
||||||
|
|
||||||
|
Note that `SET_STEPPER_CARRIAGES` performs certain internal validations
|
||||||
|
of the new printer kinematics after the change. Keep in mind that if it
|
||||||
|
detects an issue, it may leave printer kinematics in an invalid state.
|
||||||
|
This means that if `SET_STEPPER_CARRIAGES` reports an error, it is unsafe
|
||||||
|
to issue other GCode commands, and the user must inspect the error message
|
||||||
|
and either fix the problem, or manually restore the previous stepper(s)
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
Since `SET_STEPPER_CARRIAGES` can update a configuration of a single
|
||||||
|
stepper at a time, some sequences of changes can lead to invalid
|
||||||
|
intermediate kinematic configurations, even if the final configuration
|
||||||
|
is valid. In such cases a user can pass `DISABLE_CHECKS=1` parameters to
|
||||||
|
all but the last command to disable intermediate checks. For example,
|
||||||
|
if `stepper a` and `stepper b` initially have `x-y` and `x+y` carriages
|
||||||
|
correspondingly, then the following sequence of commands will let a user
|
||||||
|
effectively swap the carriage controls:
|
||||||
|
`SET_STEPPER_CARRIAGES STEPPER=a CARRIAGES=x+y DISABLE_CHECKS=1`
|
||||||
|
and `SET_STEPPER_CARRIAGES STEPPER=b CARRIAGES=x-y`, while
|
||||||
|
still validating the final kinematics state.
|
||||||
|
|
||||||
### [hall_filament_width_sensor]
|
### [hall_filament_width_sensor]
|
||||||
|
|
||||||
The following commands are available when the
|
The following commands are available when the
|
||||||
|
|
|
@ -570,6 +570,12 @@ on a cartesian, hybrid_corexy or hybrid_corexz robot
|
||||||
- `carriage_1`: The mode of the carriage 1. Possible values are:
|
- `carriage_1`: The mode of the carriage 1. Possible values are:
|
||||||
"INACTIVE", "PRIMARY", "COPY", and "MIRROR".
|
"INACTIVE", "PRIMARY", "COPY", and "MIRROR".
|
||||||
|
|
||||||
|
On a `generic_cartesian` kinematic, the following information is
|
||||||
|
available in `dual_carriage`:
|
||||||
|
- `carriages["<carriage>"]`: The mode of the carriage `<carriage>`. Possible
|
||||||
|
values are "INACTIVE" and "PRIMARY" for the primary carriage and "INACTIVE",
|
||||||
|
"PRIMARY", "COPY", and "MIRROR" for the dual carriage.
|
||||||
|
|
||||||
## virtual_sdcard
|
## virtual_sdcard
|
||||||
|
|
||||||
The following information is available in the
|
The following information is available in the
|
||||||
|
|
|
@ -21,7 +21,7 @@ SOURCE_FILES = [
|
||||||
'pollreactor.c', 'msgblock.c', 'trdispatch.c',
|
'pollreactor.c', 'msgblock.c', 'trdispatch.c',
|
||||||
'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.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_deltesian.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c',
|
||||||
'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c',
|
'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c', 'kin_generic.c'
|
||||||
]
|
]
|
||||||
DEST_LIB = "c_helper.so"
|
DEST_LIB = "c_helper.so"
|
||||||
OTHER_FILES = [
|
OTHER_FILES = [
|
||||||
|
@ -106,6 +106,12 @@ defs_trapq = """
|
||||||
defs_kin_cartesian = """
|
defs_kin_cartesian = """
|
||||||
struct stepper_kinematics *cartesian_stepper_alloc(char axis);
|
struct stepper_kinematics *cartesian_stepper_alloc(char axis);
|
||||||
"""
|
"""
|
||||||
|
defs_kin_generic_cartesian = """
|
||||||
|
struct stepper_kinematics *generic_cartesian_stepper_alloc(double a_x
|
||||||
|
, double a_y, double a_z);
|
||||||
|
void generic_cartesian_stepper_set_coeffs(struct stepper_kinematics *sk
|
||||||
|
, double a_x, double a_y, double a_z);
|
||||||
|
"""
|
||||||
|
|
||||||
defs_kin_corexy = """
|
defs_kin_corexy = """
|
||||||
struct stepper_kinematics *corexy_stepper_alloc(char type);
|
struct stepper_kinematics *corexy_stepper_alloc(char type);
|
||||||
|
@ -224,6 +230,7 @@ defs_all = [
|
||||||
defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta,
|
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_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch,
|
||||||
defs_kin_extruder, defs_kin_shaper, defs_kin_idex,
|
defs_kin_extruder, defs_kin_shaper, defs_kin_idex,
|
||||||
|
defs_kin_generic_cartesian,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Update filenames to an absolute path
|
# Update filenames to an absolute path
|
||||||
|
|
52
klippy/chelper/kin_generic.c
Normal file
52
klippy/chelper/kin_generic.c
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// Generic cartesian kinematics stepper position calculation
|
||||||
|
//
|
||||||
|
// Copyright (C) 2024 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" // move_get_coord
|
||||||
|
|
||||||
|
struct generic_cartesian_stepper {
|
||||||
|
struct stepper_kinematics sk;
|
||||||
|
struct coord a;
|
||||||
|
};
|
||||||
|
|
||||||
|
static double
|
||||||
|
generic_cartesian_stepper_calc_position(struct stepper_kinematics *sk
|
||||||
|
, struct move *m, double move_time)
|
||||||
|
{
|
||||||
|
struct generic_cartesian_stepper *cs = container_of(
|
||||||
|
sk, struct generic_cartesian_stepper, sk);
|
||||||
|
struct coord c = move_get_coord(m, move_time);
|
||||||
|
return cs->a.x * c.x + cs->a.y * c.y + cs->a.z * c.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __visible
|
||||||
|
generic_cartesian_stepper_set_coeffs(struct stepper_kinematics *sk
|
||||||
|
, double a_x, double a_y, double a_z)
|
||||||
|
{
|
||||||
|
struct generic_cartesian_stepper *cs = container_of(
|
||||||
|
sk, struct generic_cartesian_stepper, sk);
|
||||||
|
cs->a.x = a_x;
|
||||||
|
cs->a.y = a_y;
|
||||||
|
cs->a.z = a_z;
|
||||||
|
cs->sk.active_flags = 0;
|
||||||
|
if (a_x) cs->sk.active_flags |= AF_X;
|
||||||
|
if (a_y) cs->sk.active_flags |= AF_Y;
|
||||||
|
if (a_z) cs->sk.active_flags |= AF_Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stepper_kinematics * __visible
|
||||||
|
generic_cartesian_stepper_alloc(double a_x, double a_y, double a_z)
|
||||||
|
{
|
||||||
|
struct generic_cartesian_stepper *cs = malloc(sizeof(*cs));
|
||||||
|
memset(cs, 0, sizeof(*cs));
|
||||||
|
cs->sk.calc_position_cb = generic_cartesian_stepper_calc_position;
|
||||||
|
generic_cartesian_stepper_set_coeffs(&cs->sk, a_x, a_y, a_z);
|
||||||
|
return &cs->sk;
|
||||||
|
}
|
|
@ -77,5 +77,6 @@ dual_carriage_alloc(void)
|
||||||
struct dual_carriage_stepper *dc = malloc(sizeof(*dc));
|
struct dual_carriage_stepper *dc = malloc(sizeof(*dc));
|
||||||
memset(dc, 0, sizeof(*dc));
|
memset(dc, 0, sizeof(*dc));
|
||||||
dc->m.move_t = 2. * DUMMY_T;
|
dc->m.move_t = 2. * DUMMY_T;
|
||||||
|
dc->x_scale = dc->y_scale = 1.0;
|
||||||
return &dc->sk;
|
return &dc->sk;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ class PhaseCalc:
|
||||||
class EndstopPhase:
|
class EndstopPhase:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
self.name = config.get_name().split()[1]
|
self.name = " ".join(config.get_name().split()[1:])
|
||||||
# Obtain step_distance and microsteps from stepper config section
|
# Obtain step_distance and microsteps from stepper config section
|
||||||
sconfig = config.getsection(self.name)
|
sconfig = config.getsection(self.name)
|
||||||
rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig)
|
rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig)
|
||||||
|
@ -118,7 +118,7 @@ class EndstopPhase:
|
||||||
return delta * self.step_dist
|
return delta * self.step_dist
|
||||||
def handle_home_rails_end(self, homing_state, rails):
|
def handle_home_rails_end(self, homing_state, rails):
|
||||||
for rail in rails:
|
for rail in rails:
|
||||||
stepper = rail.get_steppers()[0]
|
stepper = rail.get_endstops()[0][0].get_steppers()[0]
|
||||||
if stepper.get_name() == self.name:
|
if stepper.get_name() == self.name:
|
||||||
trig_mcu_pos = homing_state.get_trigger_position(self.name)
|
trig_mcu_pos = homing_state.get_trigger_position(self.name)
|
||||||
align = self.align_endstop(rail)
|
align = self.align_endstop(rail)
|
||||||
|
|
|
@ -45,7 +45,7 @@ class StepperPosition:
|
||||||
class HomingMove:
|
class HomingMove:
|
||||||
def __init__(self, printer, endstops, toolhead=None):
|
def __init__(self, printer, endstops, toolhead=None):
|
||||||
self.printer = printer
|
self.printer = printer
|
||||||
self.endstops = endstops
|
self.endstops = [es for es in endstops if es[0].get_steppers()]
|
||||||
if toolhead is None:
|
if toolhead is None:
|
||||||
toolhead = printer.lookup_object('toolhead')
|
toolhead = printer.lookup_object('toolhead')
|
||||||
self.toolhead = toolhead
|
self.toolhead = toolhead
|
||||||
|
@ -71,7 +71,9 @@ class HomingMove:
|
||||||
sname = stepper.get_name()
|
sname = stepper.get_name()
|
||||||
kin_spos[sname] += offsets.get(sname, 0) * stepper.get_step_dist()
|
kin_spos[sname] += offsets.get(sname, 0) * stepper.get_step_dist()
|
||||||
thpos = self.toolhead.get_position()
|
thpos = self.toolhead.get_position()
|
||||||
return list(kin.calc_position(kin_spos))[:3] + thpos[3:]
|
cpos = kin.calc_position(kin_spos)
|
||||||
|
return [cp if cp is not None else tp
|
||||||
|
for cp, tp in zip(cpos, thpos[:3])] + thpos[3:]
|
||||||
def homing_move(self, movepos, speed, probe_pos=False,
|
def homing_move(self, movepos, speed, probe_pos=False,
|
||||||
triggered=True, check_triggered=True):
|
triggered=True, check_triggered=True):
|
||||||
# Notify start of homing/probing move
|
# Notify start of homing/probing move
|
||||||
|
@ -233,6 +235,10 @@ class Homing:
|
||||||
for s in kin.get_steppers()}
|
for s in kin.get_steppers()}
|
||||||
newpos = kin.calc_position(kin_spos)
|
newpos = kin.calc_position(kin_spos)
|
||||||
for axis in force_axes:
|
for axis in force_axes:
|
||||||
|
if newpos[axis] is None:
|
||||||
|
raise self.printer.command_error(
|
||||||
|
"Cannot determine position of toolhead on "
|
||||||
|
"axis %s after homing" % "xyz"[axis])
|
||||||
homepos[axis] = newpos[axis]
|
homepos[axis] = newpos[axis]
|
||||||
self.toolhead.set_position(homepos)
|
self.toolhead.set_position(homepos)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ class ManualStepper:
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
if config.get('endstop_pin', None) is not None:
|
if config.get('endstop_pin', None) is not None:
|
||||||
self.can_home = True
|
self.can_home = True
|
||||||
self.rail = stepper.PrinterRail(
|
self.rail = stepper.LookupRail(
|
||||||
config, need_position_minmax=False, default_position_endstop=0.)
|
config, need_position_minmax=False, default_position_endstop=0.)
|
||||||
self.steppers = self.rail.get_steppers()
|
self.steppers = self.rail.get_steppers()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -177,6 +177,9 @@ def lookup_minimum_z(config):
|
||||||
if config.has_section('stepper_z'):
|
if config.has_section('stepper_z'):
|
||||||
zconfig = config.getsection('stepper_z')
|
zconfig = config.getsection('stepper_z')
|
||||||
return zconfig.getfloat('position_min', 0., note_valid=False)
|
return zconfig.getfloat('position_min', 0., note_valid=False)
|
||||||
|
elif config.has_section('carriage z'):
|
||||||
|
zconfig = config.getsection('carriage z')
|
||||||
|
return zconfig.getfloat('position_min', 0., note_valid=False)
|
||||||
pconfig = config.getsection('printer')
|
pconfig = config.getsection('printer')
|
||||||
return pconfig.getfloat('minimum_z_position', 0., note_valid=False)
|
return pconfig.getfloat('minimum_z_position', 0., note_valid=False)
|
||||||
|
|
||||||
|
|
|
@ -29,14 +29,10 @@ class CartKinematics:
|
||||||
self.rails.append(stepper.LookupMultiRail(dc_config))
|
self.rails.append(stepper.LookupMultiRail(dc_config))
|
||||||
self.rails[3].setup_itersolve('cartesian_stepper_alloc',
|
self.rails[3].setup_itersolve('cartesian_stepper_alloc',
|
||||||
dc_axis.encode())
|
dc_axis.encode())
|
||||||
dc_rail_0 = idex_modes.DualCarriagesRail(
|
|
||||||
self.rails[self.dual_carriage_axis],
|
|
||||||
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(
|
self.dc_module = idex_modes.DualCarriages(
|
||||||
dc_config, dc_rail_0, dc_rail_1,
|
self.printer, [self.rails[self.dual_carriage_axis]],
|
||||||
axis=self.dual_carriage_axis)
|
[self.rails[3]], axes=[self.dual_carriage_axis],
|
||||||
|
safe_dist=config.getfloat('safe_distance', None, minval=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)
|
||||||
|
@ -52,9 +48,10 @@ class CartKinematics:
|
||||||
def calc_position(self, stepper_positions):
|
def calc_position(self, stepper_positions):
|
||||||
rails = self.rails
|
rails = self.rails
|
||||||
if self.dc_module:
|
if self.dc_module:
|
||||||
primary_rail = self.dc_module.get_primary_rail().get_rail()
|
primary_rail = self.dc_module.get_primary_rail(
|
||||||
rails = (rails[:self.dc_module.axis] +
|
self.dual_carriage_axis)
|
||||||
[primary_rail] + rails[self.dc_module.axis+1:])
|
rails = (rails[:self.dual_carriage_axis] +
|
||||||
|
[primary_rail] + rails[self.dual_carriage_axis+1:])
|
||||||
return [stepper_positions[rail.get_name()] for rail in rails]
|
return [stepper_positions[rail.get_name()] for rail in rails]
|
||||||
def update_limits(self, i, range):
|
def update_limits(self, i, range):
|
||||||
l, h = self.limits[i]
|
l, h = self.limits[i]
|
||||||
|
@ -67,8 +64,8 @@ class CartKinematics:
|
||||||
rail.set_position(newpos)
|
rail.set_position(newpos)
|
||||||
for axis_name in homing_axes:
|
for axis_name in homing_axes:
|
||||||
axis = "xyz".index(axis_name)
|
axis = "xyz".index(axis_name)
|
||||||
if self.dc_module and axis == self.dc_module.axis:
|
if self.dc_module and axis == self.dual_carriage_axis:
|
||||||
rail = self.dc_module.get_primary_rail().get_rail()
|
rail = self.dc_module.get_primary_rail(self.dual_carriage_axis)
|
||||||
else:
|
else:
|
||||||
rail = self.rails[axis]
|
rail = self.rails[axis]
|
||||||
self.limits[axis] = rail.get_range()
|
self.limits[axis] = rail.get_range()
|
||||||
|
@ -93,7 +90,7 @@ class CartKinematics:
|
||||||
# Each axis is homed independently and in order
|
# Each axis is homed independently and in order
|
||||||
for axis in homing_state.get_axes():
|
for axis in homing_state.get_axes():
|
||||||
if self.dc_module is not None and axis == self.dual_carriage_axis:
|
if self.dc_module is not None and axis == self.dual_carriage_axis:
|
||||||
self.dc_module.home(homing_state)
|
self.dc_module.home(homing_state, self.dual_carriage_axis)
|
||||||
else:
|
else:
|
||||||
self.home_axis(homing_state, axis, self.rails[axis])
|
self.home_axis(homing_state, axis, self.rails[axis])
|
||||||
def _check_endstops(self, move):
|
def _check_endstops(self, move):
|
||||||
|
|
|
@ -17,10 +17,10 @@ class DeltesianKinematics:
|
||||||
self.rails = [None] * 3
|
self.rails = [None] * 3
|
||||||
stepper_configs = [config.getsection('stepper_' + s)
|
stepper_configs = [config.getsection('stepper_' + s)
|
||||||
for s in ['left', 'right', 'y']]
|
for s in ['left', 'right', 'y']]
|
||||||
self.rails[0] = stepper.PrinterRail(
|
self.rails[0] = stepper.LookupRail(
|
||||||
stepper_configs[0], need_position_minmax = False)
|
stepper_configs[0], need_position_minmax = False)
|
||||||
def_pos_es = self.rails[0].get_homing_info().position_endstop
|
def_pos_es = self.rails[0].get_homing_info().position_endstop
|
||||||
self.rails[1] = stepper.PrinterRail(
|
self.rails[1] = stepper.LookupRail(
|
||||||
stepper_configs[1], need_position_minmax = False,
|
stepper_configs[1], need_position_minmax = False,
|
||||||
default_position_endstop = def_pos_es)
|
default_position_endstop = def_pos_es)
|
||||||
self.rails[2] = stepper.LookupMultiRail(stepper_configs[2])
|
self.rails[2] = stepper.LookupMultiRail(stepper_configs[2])
|
||||||
|
|
358
klippy/kinematics/generic_cartesian.py
Normal file
358
klippy/kinematics/generic_cartesian.py
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
# Code for generic handling the kinematics of cartesian-like printers
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024-2025 Dmitry Butyugin <dmbutyugin@google.com>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
import copy, itertools, logging, math
|
||||||
|
import gcode, mathutil, stepper
|
||||||
|
from . import idex_modes
|
||||||
|
from . import kinematic_stepper as ks
|
||||||
|
|
||||||
|
def mat_mul(a, b):
|
||||||
|
if len(a[0]) != len(b):
|
||||||
|
return None
|
||||||
|
res = []
|
||||||
|
for i in range(len(a)):
|
||||||
|
res.append([])
|
||||||
|
for j in range(len(b[0])):
|
||||||
|
res[i].append(sum(a[i][k] * b[k][j] for k in range(len(b))))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def mat_transp(a):
|
||||||
|
res = []
|
||||||
|
for i in range(len(a[0])):
|
||||||
|
res.append([a[j][i] for j in range(len(a))])
|
||||||
|
return res
|
||||||
|
|
||||||
|
def mat_pseudo_inverse(m):
|
||||||
|
mt = mat_transp(m)
|
||||||
|
mtm = mat_mul(mt, m)
|
||||||
|
pinv = mat_mul(mathutil.matrix_inv(mtm), mt)
|
||||||
|
return pinv
|
||||||
|
|
||||||
|
class MainCarriage:
|
||||||
|
def __init__(self, config, axis):
|
||||||
|
self.rail = stepper.GenericPrinterRail(config)
|
||||||
|
self.axis = ord(axis) - ord('x')
|
||||||
|
self.axis_name = axis
|
||||||
|
self.dual_carriage = None
|
||||||
|
def get_name(self):
|
||||||
|
return self.rail.get_name()
|
||||||
|
def get_axis(self):
|
||||||
|
return self.axis
|
||||||
|
def get_rail(self):
|
||||||
|
return self.rail
|
||||||
|
def add_stepper(self, kin_stepper):
|
||||||
|
self.rail.add_stepper(kin_stepper.get_stepper())
|
||||||
|
def is_active(self):
|
||||||
|
if self.dual_carriage is None:
|
||||||
|
return True
|
||||||
|
return self.dual_carriage.get_dc_module().is_active(self.rail)
|
||||||
|
def set_dual_carriage(self, carriage):
|
||||||
|
old_dc = self.dual_carriage
|
||||||
|
self.dual_carriage = carriage
|
||||||
|
return old_dc
|
||||||
|
def get_dual_carriage(self):
|
||||||
|
return self.dual_carriage
|
||||||
|
|
||||||
|
class ExtraCarriage:
|
||||||
|
def __init__(self, config, carriages):
|
||||||
|
self.name = config.get_name().split()[-1]
|
||||||
|
self.primary_carriage = config.getchoice('primary_carriage', carriages)
|
||||||
|
self.endstop_pin = config.get('endstop_pin')
|
||||||
|
def get_name(self):
|
||||||
|
return self.name
|
||||||
|
def get_axis(self):
|
||||||
|
return self.primary_carriage.get_axis()
|
||||||
|
def get_rail(self):
|
||||||
|
return self.primary_carriage.get_rail()
|
||||||
|
def add_stepper(self, kin_stepper):
|
||||||
|
self.get_rail().add_stepper(kin_stepper.get_stepper(),
|
||||||
|
self.endstop_pin, self.name)
|
||||||
|
|
||||||
|
class DualCarriage:
|
||||||
|
def __init__(self, config, carriages):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.rail = stepper.GenericPrinterRail(config)
|
||||||
|
self.primary_carriage = config.getchoice('primary_carriage', carriages)
|
||||||
|
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.axis = self.primary_carriage.get_axis()
|
||||||
|
if self.axis > 1:
|
||||||
|
raise config.error("Invalid axis '%s' for dual_carriage" %
|
||||||
|
self.primary_carriage.get_axis_name())
|
||||||
|
self.safe_dist = config.getfloat('safe_distance', None, minval=0.)
|
||||||
|
def get_name(self):
|
||||||
|
return self.rail.get_name()
|
||||||
|
def get_axis(self):
|
||||||
|
return self.primary_carriage.get_axis()
|
||||||
|
def get_rail(self):
|
||||||
|
return self.rail
|
||||||
|
def get_safe_dist(self):
|
||||||
|
return self.safe_dist
|
||||||
|
def get_dc_module(self):
|
||||||
|
return self.printer.lookup_object('dual_carriage')
|
||||||
|
def is_active(self):
|
||||||
|
return self.get_dc_module().is_active(self.rail)
|
||||||
|
def get_dual_carriage(self):
|
||||||
|
return self.primary_carriage
|
||||||
|
def add_stepper(self, kin_stepper):
|
||||||
|
self.rail.add_stepper(kin_stepper.get_stepper())
|
||||||
|
|
||||||
|
class GenericCartesianKinematics:
|
||||||
|
def __init__(self, toolhead, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self._load_kinematics(config)
|
||||||
|
for s in self.get_steppers():
|
||||||
|
s.set_trapq(toolhead.get_trapq())
|
||||||
|
toolhead.register_step_generator(s.generate_steps)
|
||||||
|
self.dc_module = None
|
||||||
|
if self.dc_carriages:
|
||||||
|
pcs = [dc.get_dual_carriage() for dc in self.dc_carriages]
|
||||||
|
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}
|
||||||
|
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 c in acs:
|
||||||
|
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)
|
||||||
|
self._check_kinematics(config.error)
|
||||||
|
for c in pcs:
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
self._check_kinematics(config.error)
|
||||||
|
# Setup boundary checks
|
||||||
|
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||||
|
self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,
|
||||||
|
above=0., maxval=max_velocity)
|
||||||
|
self.max_z_accel = config.getfloat('max_z_accel', max_accel,
|
||||||
|
above=0., maxval=max_accel)
|
||||||
|
self.limits = [(1.0, -1.0)] * 3
|
||||||
|
# Register gcode commands
|
||||||
|
gcode = self.printer.lookup_object('gcode')
|
||||||
|
gcode.register_command("SET_STEPPER_CARRIAGES",
|
||||||
|
self.cmd_SET_STEPPER_CARRIAGES,
|
||||||
|
desc=self.cmd_SET_STEPPER_CARRIAGES_help)
|
||||||
|
def _load_kinematics(self, config):
|
||||||
|
carriages = {a : MainCarriage(config.getsection('carriage ' + a), a)
|
||||||
|
for a in 'xyz'}
|
||||||
|
dc_carriages = []
|
||||||
|
for c in config.get_prefix_sections('dual_carriage '):
|
||||||
|
dc_carriages.append(DualCarriage(c, carriages))
|
||||||
|
for dc in dc_carriages:
|
||||||
|
name = dc.get_name()
|
||||||
|
if name in carriages:
|
||||||
|
raise config.error("Redefinition of carriage %s" % name)
|
||||||
|
carriages[name] = dc
|
||||||
|
self.carriages = dict(carriages)
|
||||||
|
self.dc_carriages = dc_carriages
|
||||||
|
ec_carriages = []
|
||||||
|
for c in config.get_prefix_sections('extra_carriage '):
|
||||||
|
ec_carriages.append(ExtraCarriage(c, carriages))
|
||||||
|
for ec in ec_carriages:
|
||||||
|
name = ec.get_name()
|
||||||
|
if name in carriages:
|
||||||
|
raise config.error("Redefinition of carriage %s" % name)
|
||||||
|
carriages[name] = ec
|
||||||
|
self.kin_steppers = self._load_steppers(config, carriages)
|
||||||
|
self.all_carriages = carriages
|
||||||
|
self._check_carriages_references(config.error)
|
||||||
|
self._check_multi_mcu_homing(config.error)
|
||||||
|
def _check_carriages_references(self, report_error):
|
||||||
|
carriages = dict(self.all_carriages)
|
||||||
|
for s in self.kin_steppers:
|
||||||
|
for c in s.get_carriages():
|
||||||
|
carriages.pop(c.get_name(), None)
|
||||||
|
if carriages:
|
||||||
|
raise report_error(
|
||||||
|
"Carriage(s) %s must be referenced by some "
|
||||||
|
"stepper(s)" % (", ".join(carriages),))
|
||||||
|
def _check_multi_mcu_homing(self, report_error):
|
||||||
|
for carriage in self.carriages.values():
|
||||||
|
for es in carriage.get_rail().get_endstops():
|
||||||
|
stepper_mcus = set([s.get_mcu() for s in es[0].get_steppers()
|
||||||
|
if s in carriage.get_rail().get_steppers()])
|
||||||
|
if len(stepper_mcus) > 1:
|
||||||
|
raise report_error("Multi-mcu homing not supported on"
|
||||||
|
" multi-mcu shared carriage %s" % es[1])
|
||||||
|
def _load_steppers(self, config, carriages):
|
||||||
|
return [ks.KinematicStepper(c, carriages)
|
||||||
|
for c in config.get_prefix_sections('stepper ')]
|
||||||
|
def get_steppers(self):
|
||||||
|
return [s.get_stepper() for s in self.kin_steppers]
|
||||||
|
def get_primary_carriages(self):
|
||||||
|
carriages = [self.carriages[a] for a in "xyz"]
|
||||||
|
if self.dc_module:
|
||||||
|
for 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
|
||||||
|
return carriages
|
||||||
|
def _get_kinematics_coeffs(self):
|
||||||
|
matr = {s.get_name() : list(s.get_kin_coeffs())
|
||||||
|
for s in self.kin_steppers}
|
||||||
|
offs = {s.get_name() : [0.] * 3 for s in self.kin_steppers}
|
||||||
|
if self.dc_module is None:
|
||||||
|
return ([matr[s.get_name()] for s in self.kin_steppers],
|
||||||
|
[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]:
|
||||||
|
m, o = self.dc_module.get_transform(c.get_rail())
|
||||||
|
for s in c.get_rail().get_steppers():
|
||||||
|
matr[s.get_name()][axis] *= m
|
||||||
|
offs[s.get_name()][axis] += o
|
||||||
|
return ([matr[s.get_name()] for s in self.kin_steppers],
|
||||||
|
[mathutil.matrix_dot(orig_matr[s.get_name()],
|
||||||
|
offs[s.get_name()])
|
||||||
|
for s in self.kin_steppers])
|
||||||
|
def _check_kinematics(self, report_error):
|
||||||
|
matr, _ = self._get_kinematics_coeffs()
|
||||||
|
det = mathutil.matrix_det(mat_mul(mat_transp(matr), matr))
|
||||||
|
if abs(det) < 0.00001:
|
||||||
|
raise report_error(
|
||||||
|
"Verify configured stepper(s) and their 'carriages' "
|
||||||
|
"specifications, the current configuration does not "
|
||||||
|
"allow independent movements of all printer axes.")
|
||||||
|
def calc_position(self, stepper_positions):
|
||||||
|
matr, offs = self._get_kinematics_coeffs()
|
||||||
|
spos = [stepper_positions[s.get_name()] for s in self.kin_steppers]
|
||||||
|
pinv = mat_pseudo_inverse(matr)
|
||||||
|
pos = mat_mul([[sp-o for sp, o in zip(spos, offs)]], mat_transp(pinv))
|
||||||
|
for i in range(3):
|
||||||
|
if not any(pinv[i]):
|
||||||
|
pos[0][i] = None
|
||||||
|
return pos[0]
|
||||||
|
def update_limits(self, i, range):
|
||||||
|
l, h = self.limits[i]
|
||||||
|
# Only update limits if this axis was already homed,
|
||||||
|
# otherwise leave in un-homed state.
|
||||||
|
if l <= h:
|
||||||
|
self.limits[i] = range
|
||||||
|
def set_position(self, newpos, homing_axes):
|
||||||
|
for s in self.kin_steppers:
|
||||||
|
s.set_position(newpos)
|
||||||
|
for axis_name in homing_axes:
|
||||||
|
axis = "xyz".index(axis_name)
|
||||||
|
for c in self.carriages.values():
|
||||||
|
if c.get_axis() == axis and c.is_active():
|
||||||
|
self.limits[axis] = c.get_rail().get_range()
|
||||||
|
break
|
||||||
|
def clear_homing_state(self, clear_axes):
|
||||||
|
for axis, axis_name in enumerate("xyz"):
|
||||||
|
if axis_name in clear_axes:
|
||||||
|
self.limits[axis] = (1.0, -1.0)
|
||||||
|
def home_axis(self, homing_state, axis, rail):
|
||||||
|
# Determine movement
|
||||||
|
position_min, position_max = rail.get_range()
|
||||||
|
hi = rail.get_homing_info()
|
||||||
|
homepos = [None, None, None, None]
|
||||||
|
homepos[axis] = hi.position_endstop
|
||||||
|
forcepos = list(homepos)
|
||||||
|
if hi.positive_dir:
|
||||||
|
forcepos[axis] -= 1.5 * (hi.position_endstop - position_min)
|
||||||
|
else:
|
||||||
|
forcepos[axis] += 1.5 * (position_max - hi.position_endstop)
|
||||||
|
# Perform homing
|
||||||
|
homing_state.home_rails([rail], forcepos, homepos)
|
||||||
|
def home(self, homing_state):
|
||||||
|
self._check_kinematics(self.printer.command_error)
|
||||||
|
# Each axis is homed independently and in order
|
||||||
|
for axis in homing_state.get_axes():
|
||||||
|
carriage = self.carriages["xyz"[axis]]
|
||||||
|
if carriage.get_dual_carriage() != None:
|
||||||
|
self.dc_module.home(homing_state, axis)
|
||||||
|
else:
|
||||||
|
self.home_axis(homing_state, axis, carriage.get_rail())
|
||||||
|
def _check_endstops(self, move):
|
||||||
|
end_pos = move.end_pos
|
||||||
|
for i in (0, 1, 2):
|
||||||
|
if (move.axes_d[i]
|
||||||
|
and (end_pos[i] < self.limits[i][0]
|
||||||
|
or end_pos[i] > self.limits[i][1])):
|
||||||
|
if self.limits[i][0] > self.limits[i][1]:
|
||||||
|
raise move.move_error("Must home axis first")
|
||||||
|
raise move.move_error()
|
||||||
|
def check_move(self, move):
|
||||||
|
limits = self.limits
|
||||||
|
xpos, ypos = move.end_pos[:2]
|
||||||
|
if (xpos < limits[0][0] or xpos > limits[0][1]
|
||||||
|
or ypos < limits[1][0] or ypos > limits[1][1]):
|
||||||
|
self._check_endstops(move)
|
||||||
|
if not move.axes_d[2]:
|
||||||
|
# Normal XY move - use defaults
|
||||||
|
return
|
||||||
|
# Move with Z - update velocity and accel for slower Z axis
|
||||||
|
self._check_endstops(move)
|
||||||
|
z_ratio = move.move_d / abs(move.axes_d[2])
|
||||||
|
move.limit_speed(
|
||||||
|
self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
|
||||||
|
def get_status(self, eventtime):
|
||||||
|
axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h]
|
||||||
|
ranges = [c.get_rail().get_range()
|
||||||
|
for c in self.get_primary_carriages()]
|
||||||
|
axes_min = gcode.Coord(*[r[0] for r in ranges], e=0.)
|
||||||
|
axes_max = gcode.Coord(*[r[1] for r in ranges], e=0.)
|
||||||
|
return {
|
||||||
|
'homed_axes': "".join(axes),
|
||||||
|
'axis_minimum': axes_min,
|
||||||
|
'axis_maximum': axes_max,
|
||||||
|
}
|
||||||
|
cmd_SET_STEPPER_CARRIAGES_help = "Set stepper carriages"
|
||||||
|
def cmd_SET_STEPPER_CARRIAGES(self, gcmd):
|
||||||
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
toolhead.flush_step_generation()
|
||||||
|
stepper_name = gcmd.get("STEPPER")
|
||||||
|
steppers = [stepper for stepper in self.kin_steppers
|
||||||
|
if stepper.get_name() == stepper_name
|
||||||
|
or stepper.get_name(short=True) == stepper_name]
|
||||||
|
if len(steppers) != 1:
|
||||||
|
raise gcmd.error("Invalid STEPPER '%s' specified" % stepper_name)
|
||||||
|
stepper = steppers[0]
|
||||||
|
carriages_str = gcmd.get("CARRIAGES").lower()
|
||||||
|
validate = not gcmd.get_int("DISABLE_CHECKS", 0)
|
||||||
|
old_carriages = stepper.get_carriages()
|
||||||
|
old_kin_coeffs = stepper.get_kin_coeffs()
|
||||||
|
stepper.update_carriages(carriages_str, self.all_carriages, gcmd.error)
|
||||||
|
new_carriages = stepper.get_carriages()
|
||||||
|
if new_carriages != old_carriages:
|
||||||
|
stepper.update_kin_coeffs(old_kin_coeffs)
|
||||||
|
raise gcmd.error("SET_STEPPER_CARRIAGES cannot add or remove "
|
||||||
|
"carriages that the stepper controls")
|
||||||
|
pos = toolhead.get_position()
|
||||||
|
stepper.set_position(pos)
|
||||||
|
if not validate:
|
||||||
|
return
|
||||||
|
if self.dc_module:
|
||||||
|
dc_state = self.dc_module.save_dual_carriage_state()
|
||||||
|
pcs = [dc.get_dual_carriage() for dc in self.dc_carriages]
|
||||||
|
axes = [dc.get_axis() for dc in self.dc_carriages]
|
||||||
|
for acs in itertools.product(*zip(pcs, self.dc_carriages)):
|
||||||
|
for c in acs:
|
||||||
|
self.dc_module.get_dc_rail_wrapper(c.get_rail()).activate(
|
||||||
|
idex_modes.PRIMARY, pos)
|
||||||
|
self.dc_module.get_dc_rail_wrapper(
|
||||||
|
c.get_dual_carriage().get_rail()).inactivate(pos)
|
||||||
|
self._check_kinematics(gcmd.error)
|
||||||
|
self.dc_module.restore_dual_carriage_state(dc_state, move=0)
|
||||||
|
else:
|
||||||
|
self._check_kinematics(gcmd.error)
|
||||||
|
|
||||||
|
def load_kinematics(toolhead, config):
|
||||||
|
return GenericCartesianKinematics(toolhead, config)
|
|
@ -12,7 +12,7 @@ class HybridCoreXYKinematics:
|
||||||
def __init__(self, toolhead, config):
|
def __init__(self, toolhead, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
# itersolve parameters
|
# itersolve parameters
|
||||||
self.rails = [ stepper.PrinterRail(config.getsection('stepper_x')),
|
self.rails = [ stepper.LookupRail(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[1].get_endstops()[0][0].add_stepper(
|
self.rails[1].get_endstops()[0][0].add_stepper(
|
||||||
|
@ -29,16 +29,13 @@ class HybridCoreXYKinematics:
|
||||||
# dummy for cartesian config users
|
# dummy for cartesian config users
|
||||||
dc_config.getchoice('axis', ['x'], default='x')
|
dc_config.getchoice('axis', ['x'], default='x')
|
||||||
# setup second dual carriage rail
|
# setup second dual carriage rail
|
||||||
self.rails.append(stepper.PrinterRail(dc_config))
|
self.rails.append(stepper.LookupRail(dc_config))
|
||||||
self.rails[1].get_endstops()[0][0].add_stepper(
|
self.rails[1].get_endstops()[0][0].add_stepper(
|
||||||
self.rails[3].get_steppers()[0])
|
self.rails[3].get_steppers()[0])
|
||||||
self.rails[3].setup_itersolve('corexy_stepper_alloc', b'+')
|
self.rails[3].setup_itersolve('corexy_stepper_alloc', b'+')
|
||||||
dc_rail_0 = idex_modes.DualCarriagesRail(
|
|
||||||
self.rails[0], axis=0, active=True)
|
|
||||||
dc_rail_1 = idex_modes.DualCarriagesRail(
|
|
||||||
self.rails[3], axis=0, active=False)
|
|
||||||
self.dc_module = idex_modes.DualCarriages(
|
self.dc_module = idex_modes.DualCarriages(
|
||||||
dc_config, dc_rail_0, dc_rail_1, axis=0)
|
self.printer, [self.rails[0]], [self.rails[3]], axes=[0],
|
||||||
|
safe_dist=config.getfloat('safe_distance', None, minval=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)
|
||||||
|
@ -69,8 +66,8 @@ class HybridCoreXYKinematics:
|
||||||
rail.set_position(newpos)
|
rail.set_position(newpos)
|
||||||
for axis_name in homing_axes:
|
for axis_name in homing_axes:
|
||||||
axis = "xyz".index(axis_name)
|
axis = "xyz".index(axis_name)
|
||||||
if self.dc_module and axis == self.dc_module.axis:
|
if self.dc_module and axis == 0:
|
||||||
rail = self.dc_module.get_primary_rail().get_rail()
|
rail = self.dc_module.get_primary_rail(axis)
|
||||||
else:
|
else:
|
||||||
rail = self.rails[axis]
|
rail = self.rails[axis]
|
||||||
self.limits[axis] = rail.get_range()
|
self.limits[axis] = rail.get_range()
|
||||||
|
@ -93,7 +90,7 @@ class HybridCoreXYKinematics:
|
||||||
def home(self, homing_state):
|
def home(self, homing_state):
|
||||||
for axis in homing_state.get_axes():
|
for axis in homing_state.get_axes():
|
||||||
if self.dc_module is not None and axis == 0:
|
if self.dc_module is not None and axis == 0:
|
||||||
self.dc_module.home(homing_state)
|
self.dc_module.home(homing_state, axis)
|
||||||
else:
|
else:
|
||||||
self.home_axis(homing_state, axis, self.rails[axis])
|
self.home_axis(homing_state, axis, self.rails[axis])
|
||||||
def _check_endstops(self, move):
|
def _check_endstops(self, move):
|
||||||
|
|
|
@ -12,7 +12,7 @@ class HybridCoreXZKinematics:
|
||||||
def __init__(self, toolhead, config):
|
def __init__(self, toolhead, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
# itersolve parameters
|
# itersolve parameters
|
||||||
self.rails = [ stepper.PrinterRail(config.getsection('stepper_x')),
|
self.rails = [ stepper.LookupRail(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[2].get_endstops()[0][0].add_stepper(
|
||||||
|
@ -29,16 +29,13 @@ class HybridCoreXZKinematics:
|
||||||
# dummy for cartesian config users
|
# dummy for cartesian config users
|
||||||
dc_config.getchoice('axis', ['x'], default='x')
|
dc_config.getchoice('axis', ['x'], default='x')
|
||||||
# setup second dual carriage rail
|
# setup second dual carriage rail
|
||||||
self.rails.append(stepper.PrinterRail(dc_config))
|
self.rails.append(stepper.LookupRail(dc_config))
|
||||||
self.rails[2].get_endstops()[0][0].add_stepper(
|
self.rails[2].get_endstops()[0][0].add_stepper(
|
||||||
self.rails[3].get_steppers()[0])
|
self.rails[3].get_steppers()[0])
|
||||||
self.rails[3].setup_itersolve('corexz_stepper_alloc', b'+')
|
self.rails[3].setup_itersolve('corexz_stepper_alloc', b'+')
|
||||||
dc_rail_0 = idex_modes.DualCarriagesRail(
|
|
||||||
self.rails[0], axis=0, active=True)
|
|
||||||
dc_rail_1 = idex_modes.DualCarriagesRail(
|
|
||||||
self.rails[3], axis=0, active=False)
|
|
||||||
self.dc_module = idex_modes.DualCarriages(
|
self.dc_module = idex_modes.DualCarriages(
|
||||||
dc_config, dc_rail_0, dc_rail_1, axis=0)
|
self.printer, [self.rails[0]], [self.rails[3]], axes=[0],
|
||||||
|
safe_dist=config.getfloat('safe_distance', None, minval=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)
|
||||||
|
@ -69,8 +66,8 @@ class HybridCoreXZKinematics:
|
||||||
rail.set_position(newpos)
|
rail.set_position(newpos)
|
||||||
for axis_name in homing_axes:
|
for axis_name in homing_axes:
|
||||||
axis = "xyz".index(axis_name)
|
axis = "xyz".index(axis_name)
|
||||||
if self.dc_module and axis == self.dc_module.axis:
|
if self.dc_module and axis == 0:
|
||||||
rail = self.dc_module.get_primary_rail().get_rail()
|
rail = self.dc_module.get_primary_rail(axis)
|
||||||
else:
|
else:
|
||||||
rail = self.rails[axis]
|
rail = self.rails[axis]
|
||||||
self.limits[axis] = rail.get_range()
|
self.limits[axis] = rail.get_range()
|
||||||
|
@ -93,7 +90,7 @@ class HybridCoreXZKinematics:
|
||||||
def home(self, homing_state):
|
def home(self, homing_state):
|
||||||
for axis in homing_state.get_axes():
|
for axis in homing_state.get_axes():
|
||||||
if self.dc_module is not None and axis == 0:
|
if self.dc_module is not None and axis == 0:
|
||||||
self.dc_module.home(homing_state)
|
self.dc_module.home(homing_state, axis)
|
||||||
else:
|
else:
|
||||||
self.home_axis(homing_state, axis, self.rails[axis])
|
self.home_axis(homing_state, axis, self.rails[axis])
|
||||||
def _check_endstops(self, move):
|
def _check_endstops(self, move):
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# Support for duplication and mirroring modes for IDEX printers
|
# Support for duplication and mirroring modes for IDEX printers
|
||||||
#
|
#
|
||||||
# Copyright (C) 2021 Fabrice Gallet <tircown@gmail.com>
|
# Copyright (C) 2021 Fabrice Gallet <tircown@gmail.com>
|
||||||
# Copyright (C) 2023 Dmitry Butyugin <dmbutyugin@google.com>
|
# Copyright (C) 2023-2025 Dmitry Butyugin <dmbutyugin@google.com>
|
||||||
#
|
#
|
||||||
# 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, math
|
import collections, logging, math
|
||||||
import chelper
|
import chelper
|
||||||
|
|
||||||
INACTIVE = 'INACTIVE'
|
INACTIVE = 'INACTIVE'
|
||||||
|
@ -14,18 +14,34 @@ MIRROR = 'MIRROR'
|
||||||
|
|
||||||
class DualCarriages:
|
class DualCarriages:
|
||||||
VALID_MODES = [PRIMARY, COPY, MIRROR]
|
VALID_MODES = [PRIMARY, COPY, MIRROR]
|
||||||
def __init__(self, dc_config, rail_0, rail_1, axis):
|
def __init__(self, printer, primary_rails, dual_rails, axes,
|
||||||
self.printer = dc_config.get_printer()
|
safe_dist={}):
|
||||||
self.axis = axis
|
self.printer = printer
|
||||||
self.dc = (rail_0, rail_1)
|
self.axes = axes
|
||||||
|
self._init_steppers(primary_rails + dual_rails)
|
||||||
|
self.primary_rails = [
|
||||||
|
DualCarriagesRail(c, dual_rails[i], axes[i], active=True)
|
||||||
|
for i, c in enumerate(primary_rails)]
|
||||||
|
self.dual_rails = [
|
||||||
|
DualCarriagesRail(c, primary_rails[i], axes[i], active=False)
|
||||||
|
for i, c in enumerate(dual_rails)]
|
||||||
|
self.dc_rails = collections.OrderedDict(
|
||||||
|
[(c.rail.get_name(), c)
|
||||||
|
for c in self.primary_rails + self.dual_rails])
|
||||||
self.saved_states = {}
|
self.saved_states = {}
|
||||||
safe_dist = dc_config.getfloat('safe_distance', None, minval=0.)
|
self.safe_dist = {}
|
||||||
if safe_dist is None:
|
for i, dc in enumerate(dual_rails):
|
||||||
dc0_rail = rail_0.get_rail()
|
axis = axes[i]
|
||||||
dc1_rail = rail_1.get_rail()
|
if isinstance(safe_dist, dict):
|
||||||
safe_dist = min(abs(dc0_rail.position_min - dc1_rail.position_min),
|
if axis in safe_dist:
|
||||||
abs(dc0_rail.position_max - dc1_rail.position_max))
|
self.safe_dist[axis] = safe_dist[axis]
|
||||||
self.safe_dist = safe_dist
|
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.printer.add_object('dual_carriage', self)
|
self.printer.add_object('dual_carriage', self)
|
||||||
self.printer.register_event_handler("klippy:ready", self._handle_ready)
|
self.printer.register_event_handler("klippy:ready", self._handle_ready)
|
||||||
gcode = self.printer.lookup_object('gcode')
|
gcode = self.printer.lookup_object('gcode')
|
||||||
|
@ -40,58 +56,93 @@ class DualCarriages:
|
||||||
'RESTORE_DUAL_CARRIAGE_STATE',
|
'RESTORE_DUAL_CARRIAGE_STATE',
|
||||||
self.cmd_RESTORE_DUAL_CARRIAGE_STATE,
|
self.cmd_RESTORE_DUAL_CARRIAGE_STATE,
|
||||||
desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help)
|
desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help)
|
||||||
def get_rails(self):
|
def _init_steppers(self, rails):
|
||||||
return self.dc
|
ffi_main, ffi_lib = chelper.get_ffi()
|
||||||
def get_primary_rail(self):
|
self.dc_stepper_kinematics = []
|
||||||
for rail in self.dc:
|
self.orig_stepper_kinematics = []
|
||||||
if rail.mode == PRIMARY:
|
steppers = set()
|
||||||
return rail
|
for rail in rails:
|
||||||
|
c_steppers = rail.get_steppers()
|
||||||
|
if not c_steppers:
|
||||||
|
raise self.printer.config_error(
|
||||||
|
"At least one stepper must be "
|
||||||
|
"associated with carriage: %s" % rail.get_name())
|
||||||
|
steppers.update(c_steppers)
|
||||||
|
for s in 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)
|
||||||
|
self.dc_stepper_kinematics.append(sk)
|
||||||
|
self.orig_stepper_kinematics.append(orig_sk)
|
||||||
|
s.set_stepper_kinematics(sk)
|
||||||
|
def get_axes(self):
|
||||||
|
return self.axes
|
||||||
|
def get_primary_rail(self, axis):
|
||||||
|
for dc_rail in self.dc_rails.values():
|
||||||
|
if dc_rail.mode == PRIMARY and dc_rail.axis == axis:
|
||||||
|
return dc_rail.rail
|
||||||
return None
|
return None
|
||||||
def toggle_active_dc_rail(self, index):
|
def get_dc_rail_wrapper(self, rail):
|
||||||
|
for dc_rail in self.dc_rails.values():
|
||||||
|
if dc_rail.rail == rail:
|
||||||
|
return dc_rail
|
||||||
|
return None
|
||||||
|
def get_transform(self, rail):
|
||||||
|
dc_rail = self.get_dc_rail_wrapper(rail)
|
||||||
|
if dc_rail is not None:
|
||||||
|
return (dc_rail.scale, dc_rail.offset)
|
||||||
|
return (0., 0.)
|
||||||
|
def is_active(self, rail):
|
||||||
|
dc_rail = self.get_dc_rail_wrapper(rail)
|
||||||
|
return dc_rail.is_active() if dc_rail is not None else False
|
||||||
|
def toggle_active_dc_rail(self, target_dc):
|
||||||
toolhead = self.printer.lookup_object('toolhead')
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
toolhead.flush_step_generation()
|
toolhead.flush_step_generation()
|
||||||
pos = toolhead.get_position()
|
pos = toolhead.get_position()
|
||||||
kin = toolhead.get_kinematics()
|
kin = toolhead.get_kinematics()
|
||||||
for i, dc in enumerate(self.dc):
|
axis = target_dc.axis
|
||||||
dc_rail = dc.get_rail()
|
for dc in self.dc_rails.values():
|
||||||
if i != index:
|
if dc != target_dc and dc.axis == axis and dc.is_active():
|
||||||
if dc.is_active():
|
|
||||||
dc.inactivate(pos)
|
dc.inactivate(pos)
|
||||||
target_dc = self.dc[index]
|
|
||||||
if target_dc.mode != PRIMARY:
|
if target_dc.mode != PRIMARY:
|
||||||
newpos = pos[:self.axis] + [target_dc.get_axis_position(pos)] \
|
newpos = pos[:axis] + [target_dc.get_axis_position(pos)] \
|
||||||
+ pos[self.axis+1:]
|
+ pos[axis+1:]
|
||||||
target_dc.activate(PRIMARY, newpos, old_position=pos)
|
target_dc.activate(PRIMARY, newpos, old_position=pos)
|
||||||
toolhead.set_position(newpos)
|
toolhead.set_position(newpos)
|
||||||
kin.update_limits(self.axis, target_dc.get_rail().get_range())
|
kin.update_limits(axis, target_dc.rail.get_range())
|
||||||
def home(self, homing_state):
|
def home(self, homing_state, axis):
|
||||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||||
enumerated_dcs = list(enumerate(self.dc))
|
dcs = [dc for dc in self.dc_rails.values() if dc.axis == axis]
|
||||||
if (self.get_dc_order(0, 1) > 0) != \
|
if (self.get_dc_order(dcs[0], dcs[1]) > 0) != \
|
||||||
self.dc[0].get_rail().get_homing_info().positive_dir:
|
dcs[0].rail.get_homing_info().positive_dir:
|
||||||
# The second carriage must home first, because the carriages home in
|
# The second carriage must home first, because the carriages home in
|
||||||
# the same direction and the first carriage homes on the second one
|
# the same direction and the first carriage homes on the second one
|
||||||
enumerated_dcs.reverse()
|
dcs.reverse()
|
||||||
for i, dc_rail in enumerated_dcs:
|
for dc in dcs:
|
||||||
self.toggle_active_dc_rail(i)
|
self.toggle_active_dc_rail(dc)
|
||||||
kin.home_axis(homing_state, self.axis, dc_rail.get_rail())
|
kin.home_axis(homing_state, axis, dc.rail)
|
||||||
# Restore the original rails ordering
|
# Restore the original rails ordering
|
||||||
self.toggle_active_dc_rail(0)
|
self.toggle_active_dc_rail(dcs[0])
|
||||||
def get_status(self, eventtime=None):
|
def get_status(self, eventtime=None):
|
||||||
return {('carriage_%d' % (i,)) : dc.mode
|
status = {'carriages' : {dc.get_name() : dc.mode
|
||||||
for (i, dc) in enumerate(self.dc)}
|
for dc in self.dc_rails.values()}}
|
||||||
def get_kin_range(self, toolhead, mode):
|
if len(self.dc_rails) == 2:
|
||||||
|
status.update({('carriage_%d' % (i,)) : dc.mode
|
||||||
|
for i, dc in enumerate(self.dc_rails.values())})
|
||||||
|
return status
|
||||||
|
def get_kin_range(self, toolhead, mode, axis):
|
||||||
pos = toolhead.get_position()
|
pos = toolhead.get_position()
|
||||||
axes_pos = [dc.get_axis_position(pos) for dc in self.dc]
|
dcs = [dc for dc in self.dc_rails.values() if dc.axis == axis]
|
||||||
dc0_rail = self.dc[0].get_rail()
|
axes_pos = [dc.get_axis_position(pos) for dc in dcs]
|
||||||
dc1_rail = self.dc[1].get_rail()
|
dc0_rail = dcs[0].rail
|
||||||
if mode != PRIMARY or self.dc[0].is_active():
|
dc1_rail = dcs[1].rail
|
||||||
|
if mode != PRIMARY or dcs[0].is_active():
|
||||||
range_min = dc0_rail.position_min
|
range_min = dc0_rail.position_min
|
||||||
range_max = dc0_rail.position_max
|
range_max = dc0_rail.position_max
|
||||||
else:
|
else:
|
||||||
range_min = dc1_rail.position_min
|
range_min = dc1_rail.position_min
|
||||||
range_max = dc1_rail.position_max
|
range_max = dc1_rail.position_max
|
||||||
safe_dist = self.safe_dist
|
safe_dist = self.safe_dist[axis]
|
||||||
if not safe_dist:
|
if not safe_dist:
|
||||||
return (range_min, range_max)
|
return (range_min, range_max)
|
||||||
|
|
||||||
|
@ -101,7 +152,7 @@ class DualCarriages:
|
||||||
range_max = min(range_max,
|
range_max = min(range_max,
|
||||||
axes_pos[0] - axes_pos[1] + dc1_rail.position_max)
|
axes_pos[0] - axes_pos[1] + dc1_rail.position_max)
|
||||||
elif mode == MIRROR:
|
elif mode == MIRROR:
|
||||||
if self.get_dc_order(0, 1) > 0:
|
if self.get_dc_order(dcs[0], dcs[1]) > 0:
|
||||||
range_min = max(range_min,
|
range_min = max(range_min,
|
||||||
0.5 * (sum(axes_pos) + safe_dist))
|
0.5 * (sum(axes_pos) + safe_dist))
|
||||||
range_max = min(range_max,
|
range_max = min(range_max,
|
||||||
|
@ -113,9 +164,9 @@ class DualCarriages:
|
||||||
sum(axes_pos) - dc1_rail.position_max)
|
sum(axes_pos) - dc1_rail.position_max)
|
||||||
else:
|
else:
|
||||||
# mode == PRIMARY
|
# mode == PRIMARY
|
||||||
active_idx = 1 if self.dc[1].is_active() else 0
|
active_idx = 1 if dcs[1].is_active() else 0
|
||||||
inactive_idx = 1 - active_idx
|
inactive_idx = 1 - active_idx
|
||||||
if self.get_dc_order(active_idx, inactive_idx) > 0:
|
if self.get_dc_order(dcs[active_idx], dcs[inactive_idx]) > 0:
|
||||||
range_min = max(range_min, axes_pos[inactive_idx] + safe_dist)
|
range_min = max(range_min, axes_pos[inactive_idx] + safe_dist)
|
||||||
else:
|
else:
|
||||||
range_max = min(range_max, axes_pos[inactive_idx] - safe_dist)
|
range_max = min(range_max, axes_pos[inactive_idx] - safe_dist)
|
||||||
|
@ -131,14 +182,14 @@ class DualCarriages:
|
||||||
# which actually permits carriage motion.
|
# which actually permits carriage motion.
|
||||||
return (range_min, range_min)
|
return (range_min, range_min)
|
||||||
return (range_min, range_max)
|
return (range_min, range_max)
|
||||||
def get_dc_order(self, first, second):
|
def get_dc_order(self, first_dc, second_dc):
|
||||||
if first == second:
|
if first_dc == second_dc:
|
||||||
return 0
|
return 0
|
||||||
# Check the relative order of the first and second carriages and
|
# Check the relative order of the first and second carriages and
|
||||||
# return -1 if the first carriage position is always smaller
|
# return -1 if the first carriage position is always smaller
|
||||||
# than the second one and 1 otherwise
|
# than the second one and 1 otherwise
|
||||||
first_rail = self.dc[first].get_rail()
|
first_rail = first_dc.rail
|
||||||
second_rail = self.dc[second].get_rail()
|
second_rail = second_dc.rail
|
||||||
first_homing_info = first_rail.get_homing_info()
|
first_homing_info = first_rail.get_homing_info()
|
||||||
second_homing_info = second_rail.get_homing_info()
|
second_homing_info = second_rail.get_homing_info()
|
||||||
if first_homing_info.positive_dir != second_homing_info.positive_dir:
|
if first_homing_info.positive_dir != second_homing_info.positive_dir:
|
||||||
|
@ -148,50 +199,71 @@ class DualCarriages:
|
||||||
if first_rail.position_endstop > second_rail.position_endstop:
|
if first_rail.position_endstop > second_rail.position_endstop:
|
||||||
return 1
|
return 1
|
||||||
return -1
|
return -1
|
||||||
def activate_dc_mode(self, index, mode):
|
def activate_dc_mode(self, dc, mode):
|
||||||
toolhead = self.printer.lookup_object('toolhead')
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
toolhead.flush_step_generation()
|
toolhead.flush_step_generation()
|
||||||
kin = toolhead.get_kinematics()
|
kin = toolhead.get_kinematics()
|
||||||
|
axis = dc.axis
|
||||||
if mode == INACTIVE:
|
if mode == INACTIVE:
|
||||||
self.dc[index].inactivate(toolhead.get_position())
|
dc.inactivate(toolhead.get_position())
|
||||||
elif mode == PRIMARY:
|
elif mode == PRIMARY:
|
||||||
self.toggle_active_dc_rail(index)
|
self.toggle_active_dc_rail(dc)
|
||||||
else:
|
else:
|
||||||
self.toggle_active_dc_rail(0)
|
self.toggle_active_dc_rail(self.get_dc_rail_wrapper(dc.dual_rail))
|
||||||
self.dc[index].activate(mode, toolhead.get_position())
|
dc.activate(mode, toolhead.get_position())
|
||||||
kin.update_limits(self.axis, self.get_kin_range(toolhead, mode))
|
kin.update_limits(axis, self.get_kin_range(toolhead, mode, axis))
|
||||||
def _handle_ready(self):
|
def _handle_ready(self):
|
||||||
# Apply the transform later during Klipper initialization to make sure
|
# Apply the transform later during Klipper initialization to make sure
|
||||||
# that input shaping can pick up the correct stepper kinematic flags.
|
# that input shaping can pick up the correct stepper kinematic flags.
|
||||||
for dc in self.dc:
|
for dc_rail in self.dc_rails.values():
|
||||||
dc.apply_transform()
|
dc_rail.apply_transform()
|
||||||
cmd_SET_DUAL_CARRIAGE_help = "Configure the dual carriages mode"
|
cmd_SET_DUAL_CARRIAGE_help = "Configure the dual carriages mode"
|
||||||
def cmd_SET_DUAL_CARRIAGE(self, gcmd):
|
def cmd_SET_DUAL_CARRIAGE(self, gcmd):
|
||||||
index = gcmd.get_int('CARRIAGE', minval=0, maxval=1)
|
carriage_str = gcmd.get('CARRIAGE', None)
|
||||||
|
if carriage_str is None:
|
||||||
|
raise gcmd.error('CARRIAGE must be specified')
|
||||||
|
if carriage_str in self.dc_rails:
|
||||||
|
dc_rail = self.dc_rails[carriage_str]
|
||||||
|
else:
|
||||||
|
dc_rail = None
|
||||||
|
if len(self.dc_rails) == 2:
|
||||||
|
try:
|
||||||
|
index = int(carriage_str.strip())
|
||||||
|
if index < 0 or index > 1:
|
||||||
|
raise gcmd.error('Invalid CARRIAGE=%d index' % index)
|
||||||
|
dc_rail = (self.dual_rails if index
|
||||||
|
else self.primary_rails)[0]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if dc_rail is None:
|
||||||
|
raise gcmd.error('Invalid CARRIAGE=%s specified' % carriage_str)
|
||||||
mode = gcmd.get('MODE', PRIMARY).upper()
|
mode = gcmd.get('MODE', PRIMARY).upper()
|
||||||
if mode not in self.VALID_MODES:
|
if mode not in self.VALID_MODES:
|
||||||
raise gcmd.error("Invalid mode=%s specified" % (mode,))
|
raise gcmd.error("Invalid mode=%s specified" % (mode,))
|
||||||
if mode in [COPY, MIRROR]:
|
if mode in [COPY, MIRROR]:
|
||||||
if index == 0:
|
if dc_rail in self.primary_rails:
|
||||||
raise gcmd.error(
|
raise gcmd.error(
|
||||||
"Mode=%s is not supported for carriage=0" % (mode,))
|
"Mode=%s is not supported for carriage=%s" % (
|
||||||
|
mode, dc_rail.get_name()))
|
||||||
curtime = self.printer.get_reactor().monotonic()
|
curtime = self.printer.get_reactor().monotonic()
|
||||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||||
axis = 'xyz'[self.axis]
|
axis = 'xyz'[dc_rail.axis]
|
||||||
if axis not in kin.get_status(curtime)['homed_axes']:
|
if axis not in kin.get_status(curtime)['homed_axes']:
|
||||||
raise gcmd.error(
|
raise gcmd.error(
|
||||||
"Axis %s must be homed prior to enabling mode=%s" %
|
"Axis %s must be homed prior to enabling mode=%s" %
|
||||||
(axis, mode))
|
(axis.upper(), mode))
|
||||||
self.activate_dc_mode(index, mode)
|
self.activate_dc_mode(dc_rail, mode)
|
||||||
cmd_SAVE_DUAL_CARRIAGE_STATE_help = \
|
cmd_SAVE_DUAL_CARRIAGE_STATE_help = \
|
||||||
"Save dual carriages modes and positions"
|
"Save dual carriages modes and positions"
|
||||||
def cmd_SAVE_DUAL_CARRIAGE_STATE(self, gcmd):
|
def cmd_SAVE_DUAL_CARRIAGE_STATE(self, gcmd):
|
||||||
state_name = gcmd.get('NAME', 'default')
|
state_name = gcmd.get('NAME', 'default')
|
||||||
|
self.saved_states[state_name] = self.save_dual_carriage_state()
|
||||||
|
def save_dual_carriage_state(self):
|
||||||
pos = self.printer.lookup_object('toolhead').get_position()
|
pos = self.printer.lookup_object('toolhead').get_position()
|
||||||
self.saved_states[state_name] = {
|
return {'carriage_modes': {dc.get_name() : dc.mode
|
||||||
'carriage_modes': [dc.mode for dc in self.dc],
|
for dc in self.dc_rails.values()},
|
||||||
'axes_positions': [dc.get_axis_position(pos) for dc in self.dc],
|
'carriage_positions': {dc.get_name() : dc.get_axis_position(pos)
|
||||||
}
|
for dc in self.dc_rails.values()}}
|
||||||
cmd_RESTORE_DUAL_CARRIAGE_STATE_help = \
|
cmd_RESTORE_DUAL_CARRIAGE_STATE_help = \
|
||||||
"Restore dual carriages modes and positions"
|
"Restore dual carriages modes and positions"
|
||||||
def cmd_RESTORE_DUAL_CARRIAGE_STATE(self, gcmd):
|
def cmd_RESTORE_DUAL_CARRIAGE_STATE(self, gcmd):
|
||||||
|
@ -200,67 +272,69 @@ class DualCarriages:
|
||||||
if saved_state is None:
|
if saved_state is None:
|
||||||
raise gcmd.error("Unknown DUAL_CARRIAGE state: %s" % (state_name,))
|
raise gcmd.error("Unknown DUAL_CARRIAGE state: %s" % (state_name,))
|
||||||
move_speed = gcmd.get_float('MOVE_SPEED', 0., above=0.)
|
move_speed = gcmd.get_float('MOVE_SPEED', 0., above=0.)
|
||||||
|
move = gcmd.get_int('MOVE', 1)
|
||||||
|
self.restore_dual_carriage_state(saved_state, move, move_speed)
|
||||||
|
def restore_dual_carriage_state(self, saved_state, move, move_speed=0.):
|
||||||
toolhead = self.printer.lookup_object('toolhead')
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
toolhead.flush_step_generation()
|
toolhead.flush_step_generation()
|
||||||
if gcmd.get_int('MOVE', 1):
|
if move:
|
||||||
homing_speed = 99999999.
|
homing_speed = 99999999.
|
||||||
|
move_pos = list(toolhead.get_position())
|
||||||
cur_pos = []
|
cur_pos = []
|
||||||
for i, dc in enumerate(self.dc):
|
carriage_positions = saved_state['carriage_positions']
|
||||||
self.toggle_active_dc_rail(i)
|
dcs = list(self.dc_rails.values())
|
||||||
homing_speed = min(homing_speed, dc.get_rail().homing_speed)
|
for dc in dcs:
|
||||||
|
self.toggle_active_dc_rail(dc)
|
||||||
|
homing_speed = min(homing_speed, dc.rail.homing_speed)
|
||||||
cur_pos.append(toolhead.get_position())
|
cur_pos.append(toolhead.get_position())
|
||||||
move_pos = list(cur_pos[0])
|
dl = [carriage_positions[dc.get_name()] - cur_pos[i][dc.axis]
|
||||||
dl = [saved_state['axes_positions'][i] - cur_pos[i][self.axis]
|
for i, dc in enumerate(dcs)]
|
||||||
for i in range(2)]
|
for axis in self.axes:
|
||||||
primary_ind = 0 if abs(dl[0]) >= abs(dl[1]) else 1
|
dc_ind = [i for i, dc in enumerate(dcs) if dc.axis == axis]
|
||||||
self.toggle_active_dc_rail(primary_ind)
|
if abs(dl[dc_ind[0]]) >= abs(dl[dc_ind[1]]):
|
||||||
move_pos[self.axis] = saved_state['axes_positions'][primary_ind]
|
primary_ind, secondary_ind = dc_ind[0], dc_ind[1]
|
||||||
dc_mode = INACTIVE if min(abs(dl[0]), abs(dl[1])) < 0.000000001 \
|
else:
|
||||||
else COPY if dl[0] * dl[1] > 0 else MIRROR
|
primary_ind, secondary_ind = dc_ind[1], dc_ind[0]
|
||||||
|
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:
|
if dc_mode != INACTIVE:
|
||||||
self.dc[1-primary_ind].activate(dc_mode, cur_pos[primary_ind])
|
dcs[secondary_ind].activate(dc_mode, cur_pos[primary_ind])
|
||||||
self.dc[1-primary_ind].override_axis_scaling(
|
dcs[secondary_ind].override_axis_scaling(
|
||||||
abs(dl[1-primary_ind] / dl[primary_ind]),
|
abs(dl[secondary_ind] / dl[primary_ind]),
|
||||||
cur_pos[primary_ind])
|
cur_pos[primary_ind])
|
||||||
toolhead.manual_move(move_pos, move_speed or homing_speed)
|
toolhead.manual_move(move_pos, move_speed or homing_speed)
|
||||||
toolhead.flush_step_generation()
|
toolhead.flush_step_generation()
|
||||||
# Make sure the scaling coefficients are restored with the mode
|
# Make sure the scaling coefficients are restored with the mode
|
||||||
self.dc[0].inactivate(move_pos)
|
for dc in dcs:
|
||||||
self.dc[1].inactivate(move_pos)
|
dc.inactivate(move_pos)
|
||||||
for i, dc in enumerate(self.dc):
|
for dc in self.dc_rails.values():
|
||||||
saved_mode = saved_state['carriage_modes'][i]
|
saved_mode = saved_state['carriage_modes'][dc.get_name()]
|
||||||
self.activate_dc_mode(i, saved_mode)
|
self.activate_dc_mode(dc, saved_mode)
|
||||||
|
|
||||||
class DualCarriagesRail:
|
class DualCarriagesRail:
|
||||||
ENC_AXES = [b'x', b'y']
|
ENC_AXES = [b'x', b'y']
|
||||||
def __init__(self, rail, axis, active):
|
def __init__(self, rail, dual_rail, axis, active):
|
||||||
self.rail = rail
|
self.rail = rail
|
||||||
|
self.dual_rail = dual_rail
|
||||||
|
self.sks = [s.get_stepper_kinematics() for s in rail.get_steppers()]
|
||||||
self.axis = axis
|
self.axis = axis
|
||||||
self.mode = (INACTIVE, PRIMARY)[active]
|
self.mode = (INACTIVE, PRIMARY)[active]
|
||||||
self.offset = 0.
|
self.offset = 0.
|
||||||
self.scale = 1. if active else 0.
|
self.scale = 1. if active else 0.
|
||||||
ffi_main, ffi_lib = chelper.get_ffi()
|
def get_name(self):
|
||||||
self.dc_stepper_kinematics = []
|
return self.rail.get_name()
|
||||||
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):
|
def is_active(self):
|
||||||
return self.mode != INACTIVE
|
return self.mode != INACTIVE
|
||||||
def get_axis_position(self, position):
|
def get_axis_position(self, position):
|
||||||
return position[self.axis] * self.scale + self.offset
|
return position[self.axis] * self.scale + self.offset
|
||||||
def apply_transform(self):
|
def apply_transform(self):
|
||||||
ffi_main, ffi_lib = chelper.get_ffi()
|
ffi_main, ffi_lib = chelper.get_ffi()
|
||||||
for sk in self.dc_stepper_kinematics:
|
for sk in self.sks:
|
||||||
ffi_lib.dual_carriage_set_transform(
|
ffi_lib.dual_carriage_set_transform(
|
||||||
sk, self.ENC_AXES[self.axis], self.scale, self.offset)
|
sk, self.ENC_AXES[self.axis], self.scale, self.offset)
|
||||||
def activate(self, mode, position, old_position=None):
|
def activate(self, mode, position, old_position=None):
|
||||||
|
|
92
klippy/kinematics/kinematic_stepper.py
Normal file
92
klippy/kinematics/kinematic_stepper.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
# Kinematic stepper class for generic cartesian kinematics
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 Dmitry Butyugin <dmbutyugin@google.com>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
import logging, re
|
||||||
|
import stepper, chelper
|
||||||
|
|
||||||
|
def parse_carriages_string(carriages_str, printer_carriages, parse_error):
|
||||||
|
nxt = 0
|
||||||
|
pat = re.compile('[+-]')
|
||||||
|
coeffs = [0.] * 3
|
||||||
|
ref_carriages = []
|
||||||
|
while nxt < len(carriages_str):
|
||||||
|
match = pat.search(carriages_str, nxt+1)
|
||||||
|
end = len(carriages_str) if match is None else match.start()
|
||||||
|
term = carriages_str[nxt:end].strip()
|
||||||
|
term_lst = term.split('*')
|
||||||
|
if len(term_lst) not in [1, 2]:
|
||||||
|
raise parse_error(
|
||||||
|
"Invalid term '%s' in '%s'" % (term, carriages_str))
|
||||||
|
if len(term_lst) == 2:
|
||||||
|
try:
|
||||||
|
coeff = float(term_lst[0])
|
||||||
|
except ValueError:
|
||||||
|
raise error("Invalid float '%s'" % term_lst[0])
|
||||||
|
else:
|
||||||
|
coeff = -1. if term_lst[0].startswith('-') else 1.
|
||||||
|
if term_lst[0].startswith('-') or term_lst[0].startswith('+'):
|
||||||
|
term_lst[0] = term_lst[0][1:]
|
||||||
|
c = term_lst[-1]
|
||||||
|
if c not in printer_carriages:
|
||||||
|
raise parse_error("Invalid '%s' carriage referenced in '%s'" %
|
||||||
|
(c, carriages_str))
|
||||||
|
carriage = printer_carriages[c]
|
||||||
|
j = carriage.get_axis()
|
||||||
|
if coeffs[j]:
|
||||||
|
raise error("Carriage '%s' was referenced multiple times in '%s'" %
|
||||||
|
(c, carriages_str))
|
||||||
|
coeffs[j] = coeff
|
||||||
|
ref_carriages.append(carriage)
|
||||||
|
nxt = end
|
||||||
|
return coeffs, ref_carriages
|
||||||
|
|
||||||
|
class KinematicStepper:
|
||||||
|
def __init__(self, config, printer_carriages):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.stepper = stepper.PrinterStepper(config)
|
||||||
|
self.kin_coeffs, self.carriages = parse_carriages_string(
|
||||||
|
config.get('carriages'), printer_carriages, config.error)
|
||||||
|
if not any(self.kin_coeffs):
|
||||||
|
raise config.error(
|
||||||
|
"'%s' must provide a valid 'carriages' configuration" %
|
||||||
|
self.stepper.get_name())
|
||||||
|
self.stepper.setup_itersolve(
|
||||||
|
'generic_cartesian_stepper_alloc',
|
||||||
|
self.kin_coeffs[0], self.kin_coeffs[1], self.kin_coeffs[2])
|
||||||
|
self.stepper_sk = self.stepper.get_stepper_kinematics()
|
||||||
|
# Add stepper to the carriages it references
|
||||||
|
for sc in self.carriages:
|
||||||
|
sc.add_stepper(self)
|
||||||
|
def get_name(self, short=False):
|
||||||
|
name = self.stepper.get_name(short)
|
||||||
|
if short and name.startswith('stepper '):
|
||||||
|
return name[8:]
|
||||||
|
return name
|
||||||
|
def get_stepper(self):
|
||||||
|
return self.stepper
|
||||||
|
def get_kin_coeffs(self):
|
||||||
|
return tuple(self.kin_coeffs)
|
||||||
|
def get_active_axes(self):
|
||||||
|
return [i for i, c in enumerate(self.kin_coeffs) if c]
|
||||||
|
def get_carriages(self):
|
||||||
|
return self.carriages
|
||||||
|
def update_kin_coeffs(self, kin_coeffs):
|
||||||
|
self.kin_coeffs = kin_coeffs
|
||||||
|
ffi_main, ffi_lib = chelper.get_ffi()
|
||||||
|
ffi_lib.generic_cartesian_stepper_set_coeffs(
|
||||||
|
self.stepper_sk, kin_coeffs[0], kin_coeffs[1], kin_coeffs[2])
|
||||||
|
def update_carriages(self, carriages_str, printer_carriages, report_error):
|
||||||
|
kin_coeffs, carriages = parse_carriages_string(
|
||||||
|
carriages_str, printer_carriages,
|
||||||
|
report_error or self.printer.command_error)
|
||||||
|
if report_error is not None and not any(kin_coeffs):
|
||||||
|
raise report_error(
|
||||||
|
"A valid string that references at least one carriage"
|
||||||
|
" must be provided for '%s'" % self.get_name())
|
||||||
|
self.carriages = carriages
|
||||||
|
self.update_kin_coeffs(kin_coeffs)
|
||||||
|
def set_position(self, coord):
|
||||||
|
self.stepper.set_position(coord)
|
|
@ -11,7 +11,7 @@ class PolarKinematics:
|
||||||
# Setup axis steppers
|
# Setup axis steppers
|
||||||
stepper_bed = stepper.PrinterStepper(config.getsection('stepper_bed'),
|
stepper_bed = stepper.PrinterStepper(config.getsection('stepper_bed'),
|
||||||
units_in_radians=True)
|
units_in_radians=True)
|
||||||
rail_arm = stepper.PrinterRail(config.getsection('stepper_arm'))
|
rail_arm = stepper.LookupRail(config.getsection('stepper_arm'))
|
||||||
rail_z = stepper.LookupMultiRail(config.getsection('stepper_z'))
|
rail_z = stepper.LookupMultiRail(config.getsection('stepper_z'))
|
||||||
stepper_bed.setup_itersolve('polar_stepper_alloc', b'a')
|
stepper_bed.setup_itersolve('polar_stepper_alloc', b'a')
|
||||||
rail_arm.setup_itersolve('polar_stepper_alloc', b'r')
|
rail_arm.setup_itersolve('polar_stepper_alloc', b'r')
|
||||||
|
|
|
@ -10,14 +10,14 @@ class RotaryDeltaKinematics:
|
||||||
def __init__(self, toolhead, config):
|
def __init__(self, toolhead, config):
|
||||||
# Setup tower rails
|
# Setup tower rails
|
||||||
stepper_configs = [config.getsection('stepper_' + a) for a in 'abc']
|
stepper_configs = [config.getsection('stepper_' + a) for a in 'abc']
|
||||||
rail_a = stepper.PrinterRail(
|
rail_a = stepper.LookupRail(
|
||||||
stepper_configs[0], need_position_minmax=False,
|
stepper_configs[0], need_position_minmax=False,
|
||||||
units_in_radians=True)
|
units_in_radians=True)
|
||||||
a_endstop = rail_a.get_homing_info().position_endstop
|
a_endstop = rail_a.get_homing_info().position_endstop
|
||||||
rail_b = stepper.PrinterRail(
|
rail_b = stepper.LookupRail(
|
||||||
stepper_configs[1], need_position_minmax=False,
|
stepper_configs[1], need_position_minmax=False,
|
||||||
default_position_endstop=a_endstop, units_in_radians=True)
|
default_position_endstop=a_endstop, units_in_radians=True)
|
||||||
rail_c = stepper.PrinterRail(
|
rail_c = stepper.LookupRail(
|
||||||
stepper_configs[2], need_position_minmax=False,
|
stepper_configs[2], need_position_minmax=False,
|
||||||
default_position_endstop=a_endstop, units_in_radians=True)
|
default_position_endstop=a_endstop, units_in_radians=True)
|
||||||
self.rails = [rail_a, rail_b, rail_c]
|
self.rails = [rail_a, rail_b, rail_c]
|
||||||
|
|
|
@ -135,3 +135,18 @@ def matrix_sub(m1, m2):
|
||||||
|
|
||||||
def matrix_mul(m1, s):
|
def matrix_mul(m1, s):
|
||||||
return [m1[0]*s, m1[1]*s, m1[2]*s]
|
return [m1[0]*s, m1[1]*s, m1[2]*s]
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Matrix helper functions for 3x3 matrices
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def matrix_det(a):
|
||||||
|
x0, x1, x2 = a
|
||||||
|
return matrix_dot(x0, matrix_cross(x1, x2))
|
||||||
|
|
||||||
|
def matrix_inv(a):
|
||||||
|
x0, x1, x2 = a
|
||||||
|
inv_det = 1. / matrix_det(a)
|
||||||
|
return [matrix_mul(matrix_cross(x1, x2), inv_det),
|
||||||
|
matrix_mul(matrix_cross(x2, x0), inv_det),
|
||||||
|
matrix_mul(matrix_cross(x0, x1), inv_det)]
|
||||||
|
|
|
@ -56,7 +56,8 @@ class MCU_stepper:
|
||||||
def get_mcu(self):
|
def get_mcu(self):
|
||||||
return self._mcu
|
return self._mcu
|
||||||
def get_name(self, short=False):
|
def get_name(self, short=False):
|
||||||
if short and self._name.startswith('stepper_'):
|
if short and self._name.startswith('stepper'):
|
||||||
|
# Skip an extra symbol after 'stepper'
|
||||||
return self._name[8:]
|
return self._name[8:]
|
||||||
return self._name
|
return self._name
|
||||||
def units_in_radians(self):
|
def units_in_radians(self):
|
||||||
|
@ -315,23 +316,21 @@ def parse_step_distance(config, units_in_radians=None, note_valid=False):
|
||||||
# Stepper controlled rails
|
# Stepper controlled rails
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
# A motor control "rail" with one (or more) steppers and one (or more)
|
# A motor control carriage with one (or more) steppers and one (or more)
|
||||||
# endstops.
|
# endstops.
|
||||||
class PrinterRail:
|
class GenericPrinterRail:
|
||||||
def __init__(self, config, need_position_minmax=True,
|
def __init__(self, config, need_position_minmax=True,
|
||||||
default_position_endstop=None, units_in_radians=False):
|
default_position_endstop=None, units_in_radians=False):
|
||||||
# Primary stepper and endstop
|
|
||||||
self.stepper_units_in_radians = units_in_radians
|
self.stepper_units_in_radians = units_in_radians
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.name = config.get_name().split()[-1]
|
||||||
self.steppers = []
|
self.steppers = []
|
||||||
self.endstops = []
|
self.endstops = []
|
||||||
self.endstop_map = {}
|
self.endstop_map = {}
|
||||||
self.add_extra_stepper(config)
|
self.endstop_pin = config.get('endstop_pin')
|
||||||
mcu_stepper = self.steppers[0]
|
|
||||||
self.get_name = mcu_stepper.get_name
|
|
||||||
self.get_commanded_position = mcu_stepper.get_commanded_position
|
|
||||||
self.calc_position_from_coord = mcu_stepper.calc_position_from_coord
|
|
||||||
# Primary endstop position
|
# Primary endstop position
|
||||||
mcu_endstop = self.endstops[0][0]
|
self.query_endstops = self.printer.load_object(config, 'query_endstops')
|
||||||
|
mcu_endstop = self.lookup_endstop(self.endstop_pin, self.name)
|
||||||
if hasattr(mcu_endstop, "get_position_endstop"):
|
if hasattr(mcu_endstop, "get_position_endstop"):
|
||||||
self.position_endstop = mcu_endstop.get_position_endstop()
|
self.position_endstop = mcu_endstop.get_position_endstop()
|
||||||
elif default_position_endstop is None:
|
elif default_position_endstop is None:
|
||||||
|
@ -380,6 +379,11 @@ class PrinterRail:
|
||||||
raise config.error(
|
raise config.error(
|
||||||
"Invalid homing_positive_dir / position_endstop in '%s'"
|
"Invalid homing_positive_dir / position_endstop in '%s'"
|
||||||
% (config.get_name(),))
|
% (config.get_name(),))
|
||||||
|
def get_name(self, short=False):
|
||||||
|
if short and self.name.startswith('stepper'):
|
||||||
|
# Skip an extra symbol after 'stepper'
|
||||||
|
return self.name[8:]
|
||||||
|
return self.name
|
||||||
def get_range(self):
|
def get_range(self):
|
||||||
return self.position_min, self.position_max
|
return self.position_min, self.position_max
|
||||||
def get_homing_info(self):
|
def get_homing_info(self):
|
||||||
|
@ -394,16 +398,8 @@ class PrinterRail:
|
||||||
return list(self.steppers)
|
return list(self.steppers)
|
||||||
def get_endstops(self):
|
def get_endstops(self):
|
||||||
return list(self.endstops)
|
return list(self.endstops)
|
||||||
def add_extra_stepper(self, config):
|
def lookup_endstop(self, endstop_pin, name):
|
||||||
stepper = PrinterStepper(config, self.stepper_units_in_radians)
|
ppins = self.printer.lookup_object('pins')
|
||||||
self.steppers.append(stepper)
|
|
||||||
if self.endstops and config.get('endstop_pin', None) is None:
|
|
||||||
# No endstop defined - use primary endstop
|
|
||||||
self.endstops[0][0].add_stepper(stepper)
|
|
||||||
return
|
|
||||||
endstop_pin = config.get('endstop_pin')
|
|
||||||
printer = config.get_printer()
|
|
||||||
ppins = printer.lookup_object('pins')
|
|
||||||
pin_params = ppins.parse_pin(endstop_pin, True, True)
|
pin_params = ppins.parse_pin(endstop_pin, True, True)
|
||||||
# Normalize pin name
|
# Normalize pin name
|
||||||
pin_name = "%s:%s" % (pin_params['chip_name'], pin_params['pin'])
|
pin_name = "%s:%s" % (pin_params['chip_name'], pin_params['pin'])
|
||||||
|
@ -415,19 +411,32 @@ class PrinterRail:
|
||||||
self.endstop_map[pin_name] = {'endstop': mcu_endstop,
|
self.endstop_map[pin_name] = {'endstop': mcu_endstop,
|
||||||
'invert': pin_params['invert'],
|
'invert': pin_params['invert'],
|
||||||
'pullup': pin_params['pullup']}
|
'pullup': pin_params['pullup']}
|
||||||
name = stepper.get_name(short=True)
|
|
||||||
self.endstops.append((mcu_endstop, name))
|
self.endstops.append((mcu_endstop, name))
|
||||||
query_endstops = printer.load_object(config, 'query_endstops')
|
self.query_endstops.register_endstop(mcu_endstop, name)
|
||||||
query_endstops.register_endstop(mcu_endstop, name)
|
|
||||||
else:
|
else:
|
||||||
mcu_endstop = endstop['endstop']
|
mcu_endstop = endstop['endstop']
|
||||||
changed_invert = pin_params['invert'] != endstop['invert']
|
changed_invert = pin_params['invert'] != endstop['invert']
|
||||||
changed_pullup = pin_params['pullup'] != endstop['pullup']
|
changed_pullup = pin_params['pullup'] != endstop['pullup']
|
||||||
if changed_invert or changed_pullup:
|
if changed_invert or changed_pullup:
|
||||||
raise error("Printer rail %s shared endstop pin %s "
|
raise self.printer.config_error(
|
||||||
|
"Printer rail %s shared endstop pin %s "
|
||||||
"must specify the same pullup/invert settings" % (
|
"must specify the same pullup/invert settings" % (
|
||||||
self.get_name(), pin_name))
|
self.get_name(), pin_name))
|
||||||
|
return mcu_endstop
|
||||||
|
def add_stepper(self, stepper, endstop_pin=None, endstop_name=None):
|
||||||
|
if not self.steppers:
|
||||||
|
self.get_commanded_position = stepper.get_commanded_position
|
||||||
|
self.calc_position_from_coord = stepper.calc_position_from_coord
|
||||||
|
self.steppers.append(stepper)
|
||||||
|
if endstop_pin is not None:
|
||||||
|
mcu_endstop = self.lookup_endstop(
|
||||||
|
endstop_pin, endstop_name or stepper.get_name(short=True))
|
||||||
|
else:
|
||||||
|
mcu_endstop = self.lookup_endstop(self.endstop_pin, self.name)
|
||||||
mcu_endstop.add_stepper(stepper)
|
mcu_endstop.add_stepper(stepper)
|
||||||
|
def add_stepper_from_config(self, config):
|
||||||
|
stepper = PrinterStepper(config, self.stepper_units_in_radians)
|
||||||
|
self.add_stepper(stepper, config.get('endstop_pin', None))
|
||||||
def setup_itersolve(self, alloc_func, *params):
|
def setup_itersolve(self, alloc_func, *params):
|
||||||
for stepper in self.steppers:
|
for stepper in self.steppers:
|
||||||
stepper.setup_itersolve(alloc_func, *params)
|
stepper.setup_itersolve(alloc_func, *params)
|
||||||
|
@ -441,13 +450,21 @@ class PrinterRail:
|
||||||
for stepper in self.steppers:
|
for stepper in self.steppers:
|
||||||
stepper.set_position(coord)
|
stepper.set_position(coord)
|
||||||
|
|
||||||
|
def LookupRail(config, need_position_minmax=True,
|
||||||
|
default_position_endstop=None, units_in_radians=False):
|
||||||
|
rail = GenericPrinterRail(config, need_position_minmax,
|
||||||
|
default_position_endstop, units_in_radians)
|
||||||
|
rail.add_stepper_from_config(config)
|
||||||
|
return rail
|
||||||
|
|
||||||
# Wrapper for dual stepper motor support
|
# Wrapper for dual stepper motor support
|
||||||
def LookupMultiRail(config, need_position_minmax=True,
|
def LookupMultiRail(config, need_position_minmax=True,
|
||||||
default_position_endstop=None, units_in_radians=False):
|
default_position_endstop=None, units_in_radians=False):
|
||||||
rail = PrinterRail(config, need_position_minmax,
|
rail = LookupRail(config, need_position_minmax,
|
||||||
default_position_endstop, units_in_radians)
|
default_position_endstop, units_in_radians)
|
||||||
for i in range(1, 99):
|
for i in range(1, 99):
|
||||||
if not config.has_section(config.get_name() + str(i)):
|
if not config.has_section(config.get_name() + str(i)):
|
||||||
break
|
break
|
||||||
rail.add_extra_stepper(config.getsection(config.get_name() + str(i)))
|
rail.add_stepper_from_config(
|
||||||
|
config.getsection(config.get_name() + str(i)))
|
||||||
return rail
|
return rail
|
||||||
|
|
172
test/klippy/corexyuv.cfg
Normal file
172
test/klippy/corexyuv.cfg
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
# Test config for generic cartesian kinematics with dual carriage
|
||||||
|
[carriage x]
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PE5
|
||||||
|
|
||||||
|
[carriage y]
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PJ1
|
||||||
|
|
||||||
|
[carriage z]
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 100
|
||||||
|
endstop_pin: ^PD3
|
||||||
|
|
||||||
|
[extra_carriage z1]
|
||||||
|
primary_carriage: z
|
||||||
|
endstop_pin: ^PD2
|
||||||
|
|
||||||
|
[dual_carriage u]
|
||||||
|
primary_carriage: x
|
||||||
|
safe_distance: 70
|
||||||
|
position_endstop: 300
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PE4
|
||||||
|
|
||||||
|
[dual_carriage v]
|
||||||
|
primary_carriage: y
|
||||||
|
safe_distance: 50
|
||||||
|
position_endstop: 200
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PD4
|
||||||
|
|
||||||
|
[stepper a]
|
||||||
|
carriages: x+y
|
||||||
|
step_pin: PF0
|
||||||
|
dir_pin: PF1
|
||||||
|
enable_pin: !PD7
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper b]
|
||||||
|
carriages: u-v
|
||||||
|
step_pin: PH1
|
||||||
|
dir_pin: PH0
|
||||||
|
enable_pin: !PA1
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper c]
|
||||||
|
carriages: x-y
|
||||||
|
step_pin: PF6
|
||||||
|
dir_pin: !PF7
|
||||||
|
enable_pin: !PF2
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper d]
|
||||||
|
carriages: u+v
|
||||||
|
step_pin: PE3
|
||||||
|
dir_pin: !PH6
|
||||||
|
enable_pin: !PG5
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper z]
|
||||||
|
carriages: z
|
||||||
|
step_pin: PL3
|
||||||
|
dir_pin: PL1
|
||||||
|
enable_pin: !PK0
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8
|
||||||
|
|
||||||
|
[stepper z1]
|
||||||
|
carriages: z1
|
||||||
|
step_pin: PG1
|
||||||
|
dir_pin: PG0
|
||||||
|
enable_pin: !PH3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PA4
|
||||||
|
dir_pin: PA6
|
||||||
|
enable_pin: !PA2
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.5
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB4
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK5
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[gcode_macro PARK_extruder]
|
||||||
|
gcode:
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=y
|
||||||
|
G90
|
||||||
|
G1 X0 Y0
|
||||||
|
|
||||||
|
[gcode_macro T0]
|
||||||
|
gcode:
|
||||||
|
PARK_{printer.toolhead.extruder}
|
||||||
|
SET_SERVO SERVO=my_servo angle=100
|
||||||
|
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=y
|
||||||
|
|
||||||
|
[extruder1]
|
||||||
|
step_pin: PC1
|
||||||
|
dir_pin: PC3
|
||||||
|
enable_pin: !PC7
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.5
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK7
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[gcode_macro PARK_extruder1]
|
||||||
|
gcode:
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=v
|
||||||
|
G90
|
||||||
|
G1 X300 Y200
|
||||||
|
|
||||||
|
[gcode_macro T1]
|
||||||
|
gcode:
|
||||||
|
PARK_{printer.toolhead.extruder}
|
||||||
|
SET_SERVO SERVO=my_servo angle=50
|
||||||
|
ACTIVATE_EXTRUDER EXTRUDER=extruder1
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=v
|
||||||
|
|
||||||
|
[servo my_servo]
|
||||||
|
pin: PH4
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PH5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK6
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyACM0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: generic_cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
52
test/klippy/corexyuv.test
Normal file
52
test/klippy/corexyuv.test
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Test cases on printers with dual carriage and multiple extruders
|
||||||
|
CONFIG corexyuv.cfg
|
||||||
|
DICTIONARY atmega2560.dict
|
||||||
|
|
||||||
|
# First home the printer
|
||||||
|
G90
|
||||||
|
G28
|
||||||
|
|
||||||
|
# Perform a dummy move
|
||||||
|
G1 X10 Y20 F6000
|
||||||
|
|
||||||
|
# Activate alternate carriage
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=v
|
||||||
|
G1 X170 Y190 F6000
|
||||||
|
|
||||||
|
# Go back to main carriage on X axis
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||||
|
G1 X20 F6000
|
||||||
|
|
||||||
|
# Save dual carriage state
|
||||||
|
SAVE_DUAL_CARRIAGE_STATE
|
||||||
|
|
||||||
|
G1 Y150 F6000
|
||||||
|
|
||||||
|
# Go back to main carriage on Y axis
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=y
|
||||||
|
G1 X10 Y50 F6000
|
||||||
|
|
||||||
|
# Restore dual carriage state
|
||||||
|
RESTORE_DUAL_CARRIAGE_STATE
|
||||||
|
|
||||||
|
# Test changing extruders
|
||||||
|
G1 X5
|
||||||
|
T1
|
||||||
|
G91
|
||||||
|
G1 X-10 E.2
|
||||||
|
T0
|
||||||
|
G91
|
||||||
|
G1 X20 E.2
|
||||||
|
G90
|
||||||
|
|
||||||
|
QUERY_ENDSTOPS
|
||||||
|
|
||||||
|
# Servo tests
|
||||||
|
SET_SERVO servo=my_servo angle=160
|
||||||
|
SET_SERVO servo=my_servo angle=130
|
||||||
|
|
||||||
|
# Verify STEPPER_BUZZ
|
||||||
|
STEPPER_BUZZ STEPPER='stepper d'
|
||||||
|
STEPPER_BUZZ STEPPER=extruder
|
||||||
|
STEPPER_BUZZ STEPPER=extruder1
|
165
test/klippy/generic_cartesian.cfg
Normal file
165
test/klippy/generic_cartesian.cfg
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
# Test config for generic cartesian kinematics with dual carriage
|
||||||
|
[carriage x]
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PE5
|
||||||
|
|
||||||
|
[carriage y]
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PJ1
|
||||||
|
|
||||||
|
[extra_carriage y1]
|
||||||
|
primary_carriage: y
|
||||||
|
endstop_pin: ^PB6
|
||||||
|
|
||||||
|
[carriage z]
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 100
|
||||||
|
endstop_pin: ^PD3
|
||||||
|
|
||||||
|
[extra_carriage z1]
|
||||||
|
primary_carriage: z
|
||||||
|
endstop_pin: ^PD2
|
||||||
|
|
||||||
|
[dual_carriage u]
|
||||||
|
primary_carriage: x
|
||||||
|
position_endstop: 300
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
endstop_pin: ^PE4
|
||||||
|
|
||||||
|
[stepper stepper_x]
|
||||||
|
carriages: x+y
|
||||||
|
step_pin: PF0
|
||||||
|
dir_pin: PF1
|
||||||
|
enable_pin: !PD7
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper dual_carriage]
|
||||||
|
carriages: u-y1
|
||||||
|
step_pin: PH1
|
||||||
|
dir_pin: PH0
|
||||||
|
enable_pin: !PA1
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper stepper_y]
|
||||||
|
carriages: 1*y+0.5*z
|
||||||
|
step_pin: PF6
|
||||||
|
dir_pin: !PF7
|
||||||
|
enable_pin: !PF2
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper stepper_y1]
|
||||||
|
carriages: 1*y1-0.5*z1
|
||||||
|
step_pin: PE3
|
||||||
|
dir_pin: !PH6
|
||||||
|
enable_pin: !PG5
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
|
||||||
|
[stepper stepper_z]
|
||||||
|
carriages: z
|
||||||
|
step_pin: PL3
|
||||||
|
dir_pin: PL1
|
||||||
|
enable_pin: !PK0
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8
|
||||||
|
|
||||||
|
[stepper stepper_z1]
|
||||||
|
carriages: z1
|
||||||
|
step_pin: PG1
|
||||||
|
dir_pin: PG0
|
||||||
|
enable_pin: !PH3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PA4
|
||||||
|
dir_pin: PA6
|
||||||
|
enable_pin: !PA2
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.5
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB4
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK5
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[gcode_macro PARK_extruder]
|
||||||
|
gcode:
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||||
|
G90
|
||||||
|
G1 X0
|
||||||
|
|
||||||
|
[gcode_macro T0]
|
||||||
|
gcode:
|
||||||
|
PARK_{printer.toolhead.extruder}
|
||||||
|
SET_SERVO SERVO=my_servo angle=100
|
||||||
|
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||||
|
|
||||||
|
[extruder1]
|
||||||
|
step_pin: PC1
|
||||||
|
dir_pin: PC3
|
||||||
|
enable_pin: !PC7
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.5
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK7
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[gcode_macro PARK_extruder1]
|
||||||
|
gcode:
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
G90
|
||||||
|
G1 X300
|
||||||
|
|
||||||
|
[gcode_macro T1]
|
||||||
|
gcode:
|
||||||
|
PARK_{printer.toolhead.extruder}
|
||||||
|
SET_SERVO SERVO=my_servo angle=50
|
||||||
|
ACTIVATE_EXTRUDER EXTRUDER=extruder1
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
|
||||||
|
[servo my_servo]
|
||||||
|
pin: PH4
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PH5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK6
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyACM0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: generic_cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[input_shaper]
|
64
test/klippy/generic_cartesian.test
Normal file
64
test/klippy/generic_cartesian.test
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# Test cases on printers with dual carriage and multiple extruders
|
||||||
|
CONFIG generic_cartesian.cfg
|
||||||
|
DICTIONARY atmega2560.dict
|
||||||
|
|
||||||
|
# Configure the input shaper
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
SET_INPUT_SHAPER SHAPER_TYPE_X=ei SHAPER_FREQ_X=50 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=80
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||||
|
SET_INPUT_SHAPER SHAPER_TYPE_X=ei SHAPER_FREQ_X=50 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=80
|
||||||
|
|
||||||
|
# Then home the printer
|
||||||
|
G90
|
||||||
|
G28
|
||||||
|
|
||||||
|
# Perform a dummy move
|
||||||
|
G1 X10 F6000
|
||||||
|
|
||||||
|
# Activate alternate carriage
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
G1 X190 F6000
|
||||||
|
|
||||||
|
# Go back to main carriage
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||||
|
G1 X100 F6000
|
||||||
|
|
||||||
|
# Save dual carriage state
|
||||||
|
SAVE_DUAL_CARRIAGE_STATE
|
||||||
|
|
||||||
|
G1 X50 F6000
|
||||||
|
|
||||||
|
# Go back to alternate carriage
|
||||||
|
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||||
|
G1 X130 F6000
|
||||||
|
|
||||||
|
# Restore dual carriage state
|
||||||
|
RESTORE_DUAL_CARRIAGE_STATE MOVE=1
|
||||||
|
|
||||||
|
# Test changing extruders
|
||||||
|
G1 X5
|
||||||
|
T1
|
||||||
|
G91
|
||||||
|
G1 X-10 E.2
|
||||||
|
T0
|
||||||
|
G91
|
||||||
|
G1 X20 E.2
|
||||||
|
G90
|
||||||
|
|
||||||
|
# Test changing the stepper kinematics
|
||||||
|
SET_STEPPER_CARRIAGES STEPPER=dual_carriage CARRIAGES=u+y1
|
||||||
|
SET_STEPPER_CARRIAGES STEPPER=stepper_x CARRIAGES=x-y
|
||||||
|
|
||||||
|
G1 X30 E.2
|
||||||
|
G1 Z3
|
||||||
|
|
||||||
|
QUERY_ENDSTOPS
|
||||||
|
|
||||||
|
# Servo tests
|
||||||
|
SET_SERVO servo=my_servo angle=160
|
||||||
|
SET_SERVO servo=my_servo angle=130
|
||||||
|
|
||||||
|
# Verify STEPPER_BUZZ
|
||||||
|
STEPPER_BUZZ STEPPER='stepper dual_carriage'
|
||||||
|
STEPPER_BUZZ STEPPER=extruder
|
||||||
|
STEPPER_BUZZ STEPPER=extruder1
|
Loading…
Add table
Add a link
Reference in a new issue