mirror of
				https://github.com/Klipper3d/klipper.git
				synced 2025-10-31 18:36:09 +01: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:
		
							
								
								
									
										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 | ||||||
		Reference in New Issue
	
	Block a user