A small update: I added a new command that can be used as follows:
SET_STEPPER_KINEMATICS STEPPER='stepper a' KINEMATICS=x-0.5y
Notice that there is no *
for multiplication (this is because *
is a special character in GCodes).
The idea behind this command is that it allows one to change the stepper kinematics at run-time. This command also has an extra option DISABLE_CHECKS=1
that disables internal validity checks for the stepper and overall printer kinematics and can be useful in certain scenarios. Note that even if checks are not ignored, the stepper kinematics will likely still be updated (unless the command has syntactic errors), rendering the subsequent operation of the printer potentially dangerous. So if SET_STEPPER_KINEMATICS
command reports an error, the user is advised to inspect it carefully, and revert to the previous valid configuration manually as appropriate.
First scenario where DISABLE_CHECKS=1
can be useful is when multiple steppers must be updated. Since SET_STEPPER_KINEMATICS
command can update a single stepper at a time, partial updates may render invalid overall printer kinematics. In this case, it might be useful to use DISABLE_CHECKS=1
parameter for all but the very last update command, hence validating final printer kinematics, but ignoring the intermediate validation failures.
Second scenario I can think of is disabling a stepper, in case this generally makes sense. Can be done with SET_STEPPER_KINEMATIC STEPPER='stepper a' KINEMATICS= DISABLE_CHECKS=1
The last scenario is a very custom homing procedures. In this case, one can define carriages with endstops in potentially fake, placeholder positions:
[carriage x]
position_endstop: 200 # placeholder value
position_max: 200
endstop_pin: ^PE5
[carriage y]
position_endstop: 0 # placeholder value
position_max: 200
endstop_pin: ^PJ1
[carriage z]
position_endstop: 0 # placeholder value
position_max: 100
endstop_pin: ^PD3
The important part is to define more or less real kinematics motion range for X, Y and Z axis, but endstop pins and positions should simply match the desired homing sequence and direction of motion (as will be explained later), but endstop positions won’t be actually used to determine printer homed position. Then we define some generic steppers, e.g.
[stepper a]
kinematics: x
[stepper b]
kinematics: y
[stepper c]
kinematics: z
and then for custom homing,
[force_move]
enable_force_move: True
[homing_override]
axes: xyz
gcode:
# Prepare to home the first axis
SET_STEPPER_KINEMATICS STEPPER='stepper a' KINEMATICS=x DISABLE_CHECKS=1
SET_STEPPER_KINEMATICS STEPPER='stepper b' KINEMATICS=-x DISABLE_CHECKS=1
SET_STEPPER_KINEMATICS STEPPER='stepper c' KINEMATICS=0.5x DISABLE_CHECKS=1
G28 X
# Prepare to home the second axis
SET_STEPPER_KINEMATICS STEPPER='stepper a' KINEMATICS=2y DISABLE_CHECKS=1
SET_STEPPER_KINEMATICS STEPPER='stepper b' KINEMATICS=0.5y DISABLE_CHECKS=1
SET_STEPPER_KINEMATICS STEPPER='stepper c' KINEMATICS=-y DISABLE_CHECKS=1
G28 Y
# Prepare to home the second axis
SET_STEPPER_KINEMATICS STEPPER='stepper a' KINEMATICS= DISABLE_CHECKS=1
SET_STEPPER_KINEMATICS STEPPER='stepper b' KINEMATICS=z DISABLE_CHECKS=1
SET_STEPPER_KINEMATICS STEPPER='stepper c' KINEMATICS=z DISABLE_CHECKS=1
G28 Z
# Set the actual position after homing
SET_KINEMATIC_POSITION X=100 Y=100 Z=200
# Configure the actual kinematics for all steppers
SET_STEPPER_KINEMATICS STEPPER='stepper a' KINEMATICS=x+y-z DISABLE_CHECKS=1
SET_STEPPER_KINEMATICS STEPPER='stepper b' KINEMATICS=x-y+z DISABLE_CHECKS=1
SET_STEPPER_KINEMATICS STEPPER='stepper c' KINEMATICS=2x+2y+z
Of course, the partial stepper kinematics for each homing move must be well thought through. Also notice that per earlier recommendation, the final command to update the stepper kinematics does not disable internal checks. This allows Klipper to validate the final kinematics of the printer.
Notably, supporting the latter required me to implement an SVD to be able to compute a pseudoinverse for a rank-deficient matrix, which was quite a bit of a bother. For now I enabled to use SVD for pseudoinverse calculation for better testing, but later on I’ll switch back the calculation of it for full-rank matrices to the previous implementation with a^T*a regular inverse. Also, if we ever make numpy a standard Klipper requirement, the code will be able to call numpy.linalg.pinv
directly.