New generic_cartesian kinematics (incl. CoreXYU/CoreXYUV, generic IDEX and AWD)

A follow-up from the thread in the PR regarding inverted hybrid corexy kinematics. As per discussion there I went ahead and implemented a new generic_cartesian kinematics class (code available here). This new kinematics class allows one to define in a pretty flexible manner an arbitrary Cartesian-style kinematics. In principle, the regular cartesian, corexy, hybrid_corexy can be defined that way, but more importantly, previously unsupported kinematics such as inverted hybrid_corexy can now be supported.

Edit: I updated the first post in the thread to reflect the currently implemented proposal. The original proposal can be read here:

Original proposal

The configuration can go like this:

[printer]
kinematics: generic_cartesian
...

[rail_x]
position_endstop: 0
position_max: 300
homing_speed: 50
endstop_pin: ...

[rail_y]
position_endstop: 0
position_max: 200
homing_speed: 50
endstop_pin: ...

[rail_z]
position_endstop: 200
position_max: 200
endstop_pin: ...

[kinematic_stepper b]
kinematics: x+y
step_pin: ...
dir_pin: ...
enable_pin: !...
microsteps: 16
rotation_distance: 40

[kinematic_stepper y]
kinematics: y
step_pin: ...
dir_pin: ...
enable_pin: !...
microsteps: 16
rotation_distance: 40

[kinematic_stepper z]
kinematics: z
...

[kinematic_stepper z1]
kinematics: z
...

Basically, you should specify kinematics: generic_cartesian in [printer] section, define each of [rail_x], [rail_y] and [rail_z] (some of the configuration options were moved there from the regular [stepper_?] section (which is not used in this kinematics), and define some number of[kinematic_stepper ...] sections, one per physical stepper. kinematic_stepper names can be pretty much arbitrary. Besides the regular configuration options related to stepper pins and rotation_distance/microsteps, you must define an appropriate kinematics option, which can be a simple expression containing some of x, y or z axes joined with ā€˜+ā€™ or ā€˜-ā€™ into an expression (in principle, floating point coefficients are also supported, e.g. kinematics: y+0.5*z, but Iā€™m not sure if thereā€™s any practical use for that). You must, of course, define the steppers such that the motion of all printer axes is possible (and Klipper will validate that and give you an error if thatā€™s not the case).

IDEX configurations are also supported. For that, you define an extra ā€˜railā€™ as

[dual_carriage]
axis: x  # IDEX axis, can be x or y
safe_distance: 60  # can also be omitted
position_endstop: 300
position_max: 300
homing_speed: 50
endstop_pin: ...

Then, for the steppers that are corresponding to rail_x (dual_carriage is on the x axis) and dual_carriage you must provide carriage: ... option, e.g.

[kinematic_stepper b]
kinematics: x+y
carriage: rail_x
step_pin: ...
...

[kinematic_stepper a]
kinematics: x-y
carriage: dual_carriage
step_pin: ...
...

At least one stepper for each of the two carriages must be defined, but in principle you can define more than 1 stepper for carriage: rail_x and carriage: dual_carriage (and they donā€™t have to share the same kinematics), which can be useful for some AWD IDEX setups.

Now, thereā€™s one more point that gets tricky: homing and endstops. Homing is done as usual per axis and towards the configured position_endstop for each rail. Normally, you can define an endstop_pin for each [rail_?]. However, you may want to have an endstop per stepper instead. This can be useful, for example, on a multi-stepper Z axis, or when Y axis is driven by 2 stepper motors on each side of the Y beam. In such cases, you can omit endstop_pin in the corresponding rail_? section and specify it in all relevant kinematic_stepper sections instead. But for that to work, Klipper must know which rail the stepper belongs to. If the stepper kinematics is obvious (specifies only a single axis, e.g. kinematics: z), then nothing extra needs to be done. Otherwise, you have to provide carriage option. E.g. consider the following kinematics configuration with the correct declaration of endstop pins (this is just for illustration assuming Y axis is driven by two steppers with corexy-style kinematics mapping, I do not know any real printer that would use it):

[kinematic_stepper x]
kinematics: x
# carriage mapping is obvious from kinematics
endstop_pin: ...

[kinematic_stepper a]
kinematics: x-y
# carriage mapping is not obvious and must be explicitly set for endstop_pin to work
carriage: rail_y
endstop_pin: ...

[kinematic_stepper b]
kinematics: x+y
# carriage mapping is not obvious and must be explicitly set for endstop_pin to work
carriage: rail_y
endstop_pin: ...

One small gotcha in the current implementation is that if you have a rail with multiple steppers and endstops, and there are other steppers kinematically connected with that rail, then when homing, the steppers that are mapped to that rail will stop each on its own endstops, however the other kinematically connected steppers will stop on the first endstop declared for that rail. I believe the currently supported kinematics in Klipper would exhibit the same behavior, but since they are not much flexible, the odds of running into the printer setup that requires some different behavior are next to none. Iā€™m also not sure whether this can be a problem for generic_cartesian kinematics. But if there are good suggestions on how to conveniently express in the configuration endstop_pin(s) for the pairs rail/kinematic_stepper, I can consider that.

For a more complete and elaborate config, you can check this test configuration.

Now, the current proposal for configuration goes like this:

[printer]
kinematics: generic_cartesian
...

[carriage x]
position_endstop: 0
position_max: 300
homing_speed: 50
endstop_pin: ...

[carriage y]
position_endstop: 0
position_max: 200
homing_speed: 50
endstop_pin: ...

[carriage z]
position_endstop: 200
position_max: 200
endstop_pin: ...

[stepper stepper_b]
kinematics: x+y
step_pin: ...
dir_pin: ...
enable_pin: !...
microsteps: 16
rotation_distance: 40

[stepper stepper_y]
kinematics: y
step_pin: ...
dir_pin: ...
enable_pin: !...
microsteps: 16
rotation_distance: 40

[stepper stepper_z]
kinematics: z
...

Basically, you should specify kinematics: generic_cartesian in [printer] section, define each of [carriage x], [carriage y], and [carriage z] (some of the configuration options were moved there from the regular [stepper_?] section (which is not used in this kinematics), and define some number of [stepper ...] sections, one per physical stepper. stepper names can be pretty much arbitrary. Besides the regular configuration options related to stepper pins and rotation_distance/microsteps, you must define an appropriate kinematics option, which can be a simple expression containing some of the carriages names (e.g. x, y or z) joined with ā€˜+ā€™ or ā€˜-ā€™ into an expression (in principle, floating point coefficients are also supported, e.g. kinematics: y+0.5*z, but Iā€™m not sure if thereā€™s any practical use for that). You must, of course, define the steppers such that the motion of all printer axes is possible (and Klipper will validate that and give you an error if thatā€™s not the case).

Then, if a user wants to have an additional endstop on the axis, they will need to define, e.g.

[extra_carriage z1]
primary_carriage: z
endstop_pin: ...

[stepper stepper_z1]
kinematics: z1
...

Note that, unlike main 3 carriages, the names for extra_carriage are not forced (they only must be distinct from other carriages). And each of the defined extra_carriage must be referenced by at least one stepper in the kinematics.

IDEX configurations are also supported. They can be defined as follows using a dual_carriage:

[carriage x]
position_endstop: 0
position_max: 300
endstop_pin: ...
...

[dual_carriage u]
primary_carriage: x  # defines the corresponding IDEX axis and primary carriage
position_endstop: 300
position_max: 300
endstop_pin: ...
...

[stepper a]
kinematics: x-y
...

[stepper b]
kinematic: u+y
...

and, if needed (e.g. for AWD setups), extra steppers can be defined as, e.g.

[stepper c]
kinematics: x+y
...

[stepper d]
kinematic: u-y
...

In principle, additional extra_carriage(s) can be defined for both primary (carriage x in this case) and dual carriages with corresponding steppers, but I donā€™t think thereā€™s going to be a need for that.

Also worth noting that IDEX code has been reworked to support CoreXYUV to a degree. It can be defined as

CoreXYUV
[carriage x]
...
[carriage y]
...
[dual_carriage u]
primary_carriage: x
...
[dual_carriage v]
primary_carriage: y
...
[stepper a]
kinematics: x+y
...
[stepper b]
kinematics: u-v
...
[stepper c]
kinematics: x-y
...
[stepper d]
kinematics: u+v
...

The more complete example config can be found here.

From the API perspective, if two dual_carriage(s) are defined, SET_DUAL_CARRIAGE command will support a new parameter AXIS as SET_DUAL_CARRIAGE AXIS=<axis> CARRIAGE=[0|1] [MODE=<mode>]. AXIS can be omitted if MODE=PRIMARY (which is also the default if MODE is not specified). And then for gcode templates dual_carriage module will now export not a single mode value per carriage, but an array of length 2 - a carriage mode per axis, which can be access as, e.g. {% if printer.dual_carriage.carriage_1[0] == 'INACTIVE' %} in a macro (this is for X axis == 0).

So, If you are interested in this and want Klipper to get the support of generic cartesian kinematics, please give the code a try and share your feedback. As usual with any new configuration and kinematics: please exercise an extreme caution when doing initial testing and be ready to issue an emergency shutdown if something goes wrong. I did test the code on my IDEX printer, but it has simple cartesian kinematics at the end of the day. So while the code should work, I could not test everything, and bugs are possible. Separately, Iā€™m also open to the suggestions on how to change the configuration. And once this discussion converges to something, Iā€™ll prepare a proper documentation for this kinematics.

@koconnor @miklschmidt @HelgeKeck FYI.

6 Likes

Thanks for working on this.

I have a few high-level thoughts, but please take them ā€œwith a grain of saltā€.

Purely from a naming point of view, Iā€™d say that ā€œcarriagesā€ have ā€œendstopsā€, but ā€œsteppersā€ never have an ā€œendstopā€. That is, ā€œcarriagesā€ move on ā€œrailsā€, and ā€œsteppersā€ move ā€œcarriagesā€. A ā€œcarriageā€ has a viable movement range (position_min to position_max) and can move until it hits its ā€œendstopā€ (position_endstop).

Using that terminology, the existing cartesian ā€œ[stepper_x]ā€ section defines both a ā€œcarriageā€ and a ā€œstepperā€. A ā€œ[stepper_x1]ā€ without an ā€œendstop_pinā€ could be thought of as defining just a ā€œstepperā€ that moves the ā€œcarriageā€ defined in ā€œ[stepper_x]ā€. While a ā€œ[stepper_x1]ā€ with an ā€œendstop_pinā€ is defining both a ā€œstepperā€ and another ā€œcarriageā€ (that moves on the same ā€œx railā€).

So, if one wanted to follow the above naming convention, then Iā€™d guess a user with a ā€œ[stepper_x1]ā€ that defines an ā€œendstop_pinā€ would want to define both a ā€œ[carriage]ā€ section and a ā€œ[kinematic_stepper]ā€ section.

As an aside, one thing to consider would be to allow users to define both a ā€œcarriageā€ and a ā€œstepperā€ in the same config section if the kinematics are basic cartesian kinematics. So, for example, if the config contains ā€œ[rail_z] step_pin: PA3 ā€¦ā€ then the code could know that it is both a definition of a ā€œcarriageā€ and a ā€œkinematic_stepperā€ with ā€œkinematics: zā€. That might keep the configs a little more concise, but still allow us to avoid the weird corexy ā€œ[stepper_x]ā€ but really stepper x+y config sections that we have today.

Also, in regard to homing, for what it is worth, I now think it was a mistake that I made the original idex code perform automatic homing of idex carriages. (That is, having the code always home both the primary X carriage and the secondary X carriage on a G28 X0 command.) The homing code is quite painful, and I suspect most idex users will need to carefully manage the G28 operation anyway - so having them call ACTIVATE_CARRIAGE CARRIAGE=0; G28 X0 ; ACTIVATE_CARRIAGE CARRIAGE=1; G28 X0 is reasonable. So, if weā€™re making a whole new kinematics for future flexibility it might be worth considering reworking the dependencies on homing.

Again, just some high-level thoughts.

Cheers,
-Kevin

1 Like

Also if we make this change (which seems like a good change) then I think we should also look at deprecating the existing hybrid_corexy, hybrid_corexz, and corexz kinematics, and deprecate the existing cartesian idex configs. (Probably no need to mess with basic cartesian and basic corexy kinematics.)

-Kevin

1 Like

Disagree with this, as this does not hold for my IDEX (Voron Double Dragon). It contains two Y steppers, one on the left of the X axis and one on the right. The belt tension between A and B belts may be slightly off, pulling the X-carriage skewed. By individually homing the Y steppers each to their own endstop, I can ensure that the X axis is as straight as possible after homing.

On this printer, at least, @dmbutyugin 's interpretation of an endstop being linked to a stepper fits better.

More generally, Iā€™ve played with the corexy and idex kinematics before, and also disliked the previous naming scheme (e.g. naming the A belt stepper_x in corexy, unclear how to differentiate between the CoreXY B stepper and the normal Y stepper in a hybrid corexy, etc). I like splitting up the axis from the steppers, although Iā€™m not 100% convinced ā€œrailā€ is the appropriate name.
In configuration it would feel more naturaly to me to define axis XYZ, and then link up steppers to specific axis through kinematics, and keep rail as a collection of steppers sharing the same kinematics.
Nevertheless, the current proposal is already miles better than what we had before :slight_smile:

EDIT:
I also agree with potentially deprecating all the other corexy, corexz, hybrid_corexy, hybrid_corexz ones. Maybe it can be solved by suplying default ā€œkinematicsā€ depending on naming? e.g. kinematic_steppers starting with ā€œaā€ would, by default, get kinematics ā€œx+yā€ ?

Also, would it not be better to open a (draft) pull request so we can add comments related to specific bits of the code ?

1 Like

OK, if I understood the proposal correctly (or at least, how I would go about it), we would not loose the ability to specify multiple endstops per axis. Instead of declaring rail_?, weā€™d define a number of carriage objects:

[carriage name]
axis: # x, y or z
type: # optional, primary, dual or unset. default is unset.
endstop_pin: ...
# The rest should be, in principle, specified once per axis and type
position_min: ...
position_max: ...
position_endstop: ...
homing_speed: ...

And then the stepper can specify which carriage(s) it moves, and have corresponding stepper-axis-endstop_pin mapping. For example, for IDEX hybrid_corexy with two y steppers it could go like this:

[carriage tool0]
axis: x
type: primary
endstop_pin: ...
position_min: 0
position_max: 350
position_endstop: 0

[carriage tool1]
axis: x
type: dual
endstop_pin: ...
position_min: 50
position_max: 400
position_endstop: 400
safe_distance: 50

[carriage y]
axis: y
endstop_pin: ...
position_min: 0
position_max: 300
position_endstop: 0

[carriage y1]
axis: y
endstop_pin: ...

[kinematic_stepper x_t0]
kinematics: x-y
carriage: tool0,y
...

[kinematic_stepper x_t1]
kinematics: x+y
carriage: tool1,y1
...

[kinematic_stepper y]
kinematics: y
carriage: y
...

[kinematic_stepper y1]
kinematics: y
carriage y1
...

Basically, the rule would be, endstop position, range of motion and homing parameters (e.g. speed) must be defined in one carriage per axis per type, an axis can have only ā€˜type-lessā€™ carriages or carriages of type primary and dual, and if a stepper is connected kinematically to an axis that has more than one carriage, it must specify which one it is. I think this will cover all possible cases and will make a choice of an endstop for each homing axis explicit.

This approach, theoretically, also allows one to transparently define idex setup for both x and y axes, and therefore support printers with dual gantry, like DuelingZero. Iā€™m not sure if Iā€™m ready to commit to actually implementing it, but Iā€™ll at least entertain that idea and see if it can be done.

The only point where Iā€™d like a feedback is the following setup:

[carriage x]
axis: x
endstop_pin: ...

[carriage y]
axis: y
endstop_pin: ...

[carriage y1]
axis: y
endstop_pin: ...

[kinematic_stepper x]
kinematics: x-y
carriage: # ???
...

[kinematic_stepper y]
kinematics: y
carriage: y
...

[kinematic_stepper y1]
kinematics: y
carriage y1
...

So, the question is, what should [kinematic_stepper x] specify as a carriage in this case? In principle, it is necessary to specify the carriage for Y axis, and so, say, this would suffice:

[kinematic_stepper x]
kinematics: x-y
carriage: y

but that would just look really odd to the user, as the stepper is ultimately for X axis, but we ask them to specify Y carriage instead (as X can be inferred). One way to go about it is to request that if a user specifies at least one carriage, they must list the carriages for all axes the stepper is kinematically connected to, e.g. in this case it can be

[kinematic_stepper x]
kinematics: x-y
carriage: x,y

This becomes a bit verbose, but at least it is less weird. Another possibility to map a stepper by default to the first defined carriage on the axis, but I fear this is non-intuitive and fragile. Although in this case the user may not be able to tell in the first place which Y carriage they should specify, y or y1. I believe the current implementation of hybrid_corexy kinematics in Klipper does exactly the last option. @koconnor do you have any feedback on this?

And separately, I agree we could deprecated hybrid_corexy and hybrid_corexz kinematics, but this wonā€™t be for the first PR for sure. And Iā€™m not certain about [dual_carriage] for regular cartesian kinematics, should we also deprecated it or not. I guess we can see if that will simplify idex code considerably if we drop that support altogether.

Yeah - thatā€™s kinda what I was thinking.

Might want to consider moving type into the section name. Maybe something like: [carriage my_x_carriage] for ā€œprimaryā€ carriages, [idex_carriage my_idex] for ā€œdualā€ style carriages that can be optionally enabled/disabled at runtime, and [extra_homing_carriage my_extra] for ā€œcarriagesā€ that are tied to the primary (or idex) but have an extra endstop that is used during homing. Both a [carriage] and [idex_carriage] would define an endstop_pin and position_min/max, while an [extra_homing_carriage] would define its endstop_pin and primary carriage, but no position_min/max.

If defining [extra_homing_carriage] is too pedantic, Iā€™m open to alternatives. Also, the names Iā€™m using here are just examples.

Good question. I hadnā€™t really thought about that.

Just ā€œthinking out loudā€, can we not specify a carriage and instead specify that information in kinematics? Something like:

[kinematic_stepper printer_front_right]
kinematics: tool0 - my_y_carriage
...

[kinematic_stepper printer_front_left]
kinematics: tool1 - my_y_carriage
...

[kinematic_stepper bed_lift]
kinematics: my_z_carriage
...

Again, just some high-level thoughts.

-Kevin

EDIT: Or, equivalently, instead of eliminating carriage, eliminate kinematics. And use something like: carriages: my_x, -my_y or carriages: 0.25 * my_x_car, -0.75 * my_y_car, 0.8 * my_z_car.

Itā€™s just a ā€œnamingā€ thing. If I understand what youā€™re saying, that setup would have two y ā€œcarriagesā€ - a ā€œyā€ carriage on the left, and a ā€œyā€ carriage on the right. The left stepper moves the ā€œleft y carriageā€ and the right stepper moves the ā€œright y carriageā€. The X rail sits on the two ā€œy carriagesā€ and the ā€œx carriageā€ moves on the ā€œx railā€.

Otherwise, I guess it would help if you could show a diagram of your printer that includes the rails, carriages, steppers, and belt paths. As far as I know, it isnā€™t possible to ā€œhome a stepperā€ as steppers themselves can rotate infinitely in the clockwise or counter-clockwise direction - itā€™s always something connected to the stepper that homes. The proposal was to call ā€œthat something connected to the stepper that homesā€ a ā€œcarriageā€.

These are just some of my high-level thoughts, so let me know if Iā€™m getting something wrong.

Cheers,
-Kevin

TBH, I prefer the type to be specified as an option. This way we have a very clear differentiation between a primary carriage and a common carriage for idex setups (theyā€™ll have different type set (or unset), as opposed to having to search for idex carriage to see which axis it is). Then, technically extra carriage can also be primary or dual, so theyā€™ll have to be differentiated too, and then if we do it through name type, the number of different carriage type explodes. I will consider having [carriage ...] and [extra_carriage ...] though, especially if this simplifies validation of the configuration.

Yes, we could, but I wonder if it will be more confusing and less readable? So, anyways, assuming we have the following carriages defined,

[carriage xc]
axis: x
endstop_pin: ...

[carriage yc]
axis: y
endstop_pin: ...

[carriage y1c]
axis: y
endstop_pin: ...

it seems we have the following options to choose from:

[kinematic_stepper a1]
kinematics: x-y
carriage: xc,y1c

or

[kinematic_stepper a2]
kinematics: xc-y1c

or

[kinematic_stepper a3]
carriage: xc,-y1c

Iā€™d also like to hear the opinions of the potential users, which option they think is the best? If I were to choose, Iā€™d probaby be choosing between a2 and a1 as a personal preference.

Can you elaborate on this? What does it mean to have a type: unset carriage, and what is a ā€œcommon carriageā€? Your text above cited four possible types (optional, primary, dual or unset), but it isnā€™t clear to me what those 4 types mean.

For what it is worth, itā€™s the configuration checking that prompted my proposal for putting ā€œtypeā€ into the section name. In particular itā€™s really painful for Config_Reference.md to describe the available options when the available options depend on the setting of other options. The [display] section is an example of how complex this can get ( Configuration reference - Klipper documentation ).

As far as I know, just three carriage types would be needed, and all three would have different parameters. Something like:

# A primary carriage that is enabled at startup.
[carriage my_carriage]
axis:
endstop_pin:
#position_min: 0
position_endstop:
position_max:
#homing_speed: 5.0
#homing_retract_dist: 5.0
#homing_retract_speed:
#second_homing_speed:
#homing_positive_dir:

# An extra carriage that is tied to a primary (or idex) carriage.
# Used when an axis has multiple endstops.
[carriage_extra my_extra_carriage]
primary_carriage: my_carriage  # or "my_idex_carriage"
endstop_pin:

# An optional carriage that may be enabled or disabled at runtime.
[carriage_idex my_idex_carriage]
axis:
#safe_distance:
#endstop_pin:
#position_endstop:
#position_min:
#position_max:

Currently [dual_carriage] has safe_distance and fewer homing settings. Itā€™s also not immediately clear to me if ā€œcarriage_idexā€ should specify ā€œaxisā€ or ā€œprimary_carriageā€.

Yeah - itā€™s always hard to come up with names for these things. I donā€™t think any answer is right or wrong.

For what it is worth, option a3 has a couple of minor advantages: it makes it clear to the user that carriage names are expected, and it is easier to export to downstream clients via the api server. If itā€™s viewed as ā€œkinda a listā€ then I think we can have printer.configfile.settings.a3.carriages return [[1.0, "xc"], [-1.0, "y1c"]]. If we perform a ā€œmath formula parseā€ then not only would Klipper have to do the parsing, but potentially downstream apps would too.

Again, just some high-level ideas.

Cheers,
-Kevin

From my user perspective, I like this variant best. It breaks up the concept into digestible pieces and is more clear where the relationships are. Also from a documentation POV this seems clearer to handle.

The other two options that already mix the two concepts, IMO, requires already more background knowledge to understand.

Sorry that I wasnā€™t clear about it. There are the following combinations in my proposal:

[carriage my_c]
axis: x
type: primary
[carriage my_c]
axis: x
type: dual
[carriage my_c]
axis: x

in the last option the type is essentially unspecified. The first two options are used to declare primary/dual carriages for the axis for IDEX, the 3rd option - for the ā€œregularā€ carriages that are used on regular printers and for the carriages that do not change the behavior on IDEX carriage changes. So the documentation can say, if this carriage is one of the IDEX carriages, specify its type, otherwise, donā€™t.

OK, I havenā€™t thought about referencing the main carriages in other types of carriages. That has some advantages, I wonder if it has some downsides too? At the very least, Iā€™ll keep that option in mind (and your proposal in general).

Do we have some usecases for that downstream? And anyways, the configfile parsing is rather primitive, so AFAICT we can only parse carriage option as a list with elements like ["xc", "-y1c"] (conditional parsing of signs vs floats wouldnā€™t be trivial using getlists would be a bit of a bother). And this probably wouldnā€™t be sufficient (and for many practical uses one has to join that list with the carriage configuration).

OK, following the discussion here, I pushed a new update to my branch which follows the configuration convention below:

[printer]
kinematics: generic_cartesian
...

[carriage x]
position_endstop: 0
position_max: 300
endstop_pin: ...
...

[carriage y]
position_endstop: 0
position_max: 200
endstop_pin: ...
...

[carriage z]
position_endstop: 200
position_max: 200
endstop_pin: ...

[kinematic_stepper a]
kinematics: x-y
...

[kinematic_stepper b]
kinematics: x+y
...

[kinematic_stepper stepper_z]
kinematics: z
...

So, the user must define sections [carriage x], [carriage y], and [carriage z], which correspond to the axes referenced in their names. kinematic_stepper(s) can be named arbitrarily, and they must reference the names of the carriages in their kinematics. However, due to the 3 carriages having the names forced to x, y and z, this makes the kinematics formulae for them look pretty natural.

Then, if a user wants to have an additional endstop on the axis, they will need to define, e.g.

[extra_carriage z1]
primary_carriage: z
endstop_pin: ...

[kinematic_stepper stepper_z1]
kinematics: z1
...

Note that, unlike main 3 carriages, the names for extra_carriage are not forced (they only must be distinct from other carriages). And each of the defined extra_carriage must be referenced by at least one kinematic_stepper in the kinematics.

Then, idex can be defined as follows with a dual_carriage:

[carriage x]
position_endstop: 0
position_max: 300
endstop_pin: ...
...

[dual_carriage u]
primary_carriage: x
position_endstop: 300
position_max: 300
endstop_pin: ...
...

[kinematic_stepper a]
kinematics: x-y
...

[kinematic_stepper b]
kinematic: u+y
...

and, if needed, extra steppers can be defined as, e.g.

[kinematic_stepper c]
kinematics: x+y
...

[kinematic_stepper d]
kinematic: u-y
...

In principle, additional extra_carriage(s) can be defined for both primary (carriage x in this case) and dual carriages with corresponding steppers, but I donā€™t think thereā€™s going to be a need for that.

A config for RatRig AWD can be defined roughly as

V-Core 4 AWD
[carriage x]
...

[carriage y]
...

[extra_carriage y1]
primary_carriage: y
...

[kinematic_stepper x_p]
kinematics: x+y
...

[kinematic_stepper x_m]
kinematics: x-y  # must be the same carriage for Y axis as in x_p
...

[kinematic_stepper y]
kinematics: y
...

[kinematic_stepper y1]
kinematics: y1
...

Note that steppers x_m and x_p reference the same carriage y. This is done so that if y and y1 trigger at different times (e.g. if the beam was not parallel to Y axis prior to homing) the steppers x_m and x_p stop at the same time and will not attempt to tear belts or X carriage apart (it would be a risk if, say, x_p would reference y1 instead of y). Contrary to that, IDEX config can be

V-Core 4 IDEX
[carriage x]
...

[dual_carriage u]
primary_carriage: x
...

[carriage y]
...

[extra_carriage y1]
primary_carriage: y
...

[kinematic_stepper x_p]
kinematics: x+y
...

[kinematic_stepper x_m]
kinematics: u-y1  # u-y is also possible
...

[kinematic_stepper y]
kinematics: y
...

[kinematic_stepper y1]
kinematics: y1
...

Though since the different sides of Y beam can stop at different points in time, the carriage x and u could move unpredictably during this time between two Y endstops trigger, so it might be a good idea to first home Y, and then proceed with homing X axis for such configs.

And then I reworked IDEX kinematics module, so it should now support CoreXYUV setups configured as, e.g.

CoreXYUV
[carriage x]
...
[carriage y]
...
[dual_carriage u]
primary_carriage: x
...
[dual_carriage v]
primary_carriage: y
...
[kinematic_stepper a]
kinematics: x+y
...
[kinematic_stepper b]
kinematics: u-v
...
[kinematic_stepper c]
kinematics: x-y
...
[kinematic_stepper d]
kinematics: u+v
...

The full config can be found here, for example.

From the API perspective, if two dual_carriage(s) are defined, SET_DUAL_CARRIAGE command will support a new parameter AXIS as SET_DUAL_CARRIAGE AXIS=<axis> CARRIAGE=[0|1] [MODE=<mode>]. AXIS can be omitted if MODE=PRIMARY (which is also the default if MODE is not specified). And then for gcode templates dual_carriage will export not a single mode value per carriage, but an array of length 2 - a carriage mode per axis, which can be access as, e.g. {% if printer.dual_carriage.carriage_1[0] == 'INACTIVE' %} in a macro (this is for X axis == 0).

With this, I hope we are converging on something, so please let me know your feedback on this proposed configuration schema.

And the last point

Alas, I did not manage to do that (yet). It does seem to be a non-trivial thing to implement, as then IDEX code needs to track ā€˜non-homedā€™ state, or generic_cartesian kinematics needs to do that (but IDEX code still needs to be homing-aware, e.g. if carriage 0 is homed and active, and carriage 1 is not, then it is unsafe to move carriage 0 and, more importantly, activate copy and mirror modes). So all in all, Iā€™m not convinced that implementing this will simplify the code, but Iā€™ll try to spend some more time on this in the near future. And @koconnor I wanted to ask your opinion about macro overrides for G28. The documentation does not explicitly encourage or discourage a user from doing it AFAICT, but other existing solutions that are promoted by documentation - [safe_z_home] and [homing_override] - are fairly inflexible in my opinion, at least for IDEX generic_cartesian case. That is, safe_z_home does not necessarily solve the need for more complicated homing procedures, and homing_override requires the user to home all axes even if that makes no sense from the homing need perspective (e.g. a user looses the ability to just home G28 X Y). So, to give a concrete example, do you consider something like this:

gcode_macro G28
[gcode_macro G28]
rename_existing: G28.1
gcode:
    {% set home_all = not ('X' in params or 'Y' in params or 'Z' in params) %}
    {% if 'Z' in params or 'X' in params or home_all %}
        SAVE_DUAL_CARRIAGE_STATE NAME=homing_override_dc
        G28.1 X
        SAVE_GCODE_STATE NAME=homing_override_gc
        SET_DUAL_CARRIAGE CARRIAGE=1 MODE=mirror
        G91
        G0 X2 F1200
        RESTORE_GCODE_STATE NAME=homing_override_gc MOVE=0
        RESTORE_DUAL_CARRIAGE_STATE NAME=homing_override_dc MOVE=0
    {% endif %}
    {% if 'Z' in params or 'Y' in params or home_all %}
        G28.1 Y
    {% endif %}
    {% if 'Z' in params or home_all %}
        SAVE_GCODE_STATE NAME=homing_axis_z
        G90
        G0 Y40 F12000
        G28.1 Z
        G0 Z5 F1200
        RESTORE_GCODE_STATE NAME=homing_axis_z MOVE=0
    {% endif %}

a valid option to suggest to the users?

2 Likes

Thanks for working on this, and thanks for writing up a detailed proposal.

I have a couple of observations on config section naming. Nothing ā€œset in stoneā€ though.

Going forward I think it would be good to try to settle on a convention where the first word of a module name is the ā€œcategoryā€ and subsequent words are the ā€œspecific instance within that categoryā€. Examples of this would be ā€œtemperature_mcuā€ (the sensor is a temperature sensor and it is measuring mcu temperatures), ā€œprobe_eddy_currentā€, ā€œerror_mcuā€, and similar. We didnā€™t always do this (eg, ā€œmanual_stepperā€ and ā€œadc_temperatureā€), but I think it may still be useful to adopt a convention going forward. So, consider names like ā€œstepper_kinematicsā€ instead of ā€œkinematics_stepperā€. (Although, in this instance, just ā€œstepperā€ is probably fine.) Another example, might be ā€œstepper_extraā€ instead of ā€œextra_stepperā€.

In the future, I think we may want to encourage unique ā€œshort namesā€ for config sections - where ā€œ[mcu my_mcu]ā€ has a ā€œshort nameā€ of ā€œmy_mcuā€. We donā€™t currently enforce uniqueness for these ā€œshort namesā€ and it can lead to a lot of confusion. For example, itā€™s not unusual for some configs to declare [mcu x], [temperature_mcu x], [adxl345 x], and similar. This can be kinda confusing when one issues a command with a NAME=x parameter, as it isnā€™t always immediately clear what config section they are intending to refer to. So, something to keep in mind if we require users to name their carriages with very short names (eg, ā€œ[carriage z]ā€) as that could get confusing in reference to other config sections that the user wishes to give arbitrary names to (eg, [mcu z]).

Again, just some high level thoughts - nothing set in stone.

Cheers,
-Kevin

1 Like

Thanks for the reply and the suggestions.

FWIW, I donā€™t particularly like stepper_kinematics name as this sounds a bit unnatural, in my opinion, and suggests that this is not what it actually is (that it only defines the stepper motor kinematics, while in fact it defines the whole stepper). I donā€™t have a better suggestion though, so if you happen to have any, please let me know. Otherwise, Iā€™m also fine just shortening it to [stepper stepper_name], since this wonā€™t interfere with [stepper_x] and such from other kinematics. Then I probably wonā€™t be able to have extras/stepper.py module, but I could move KinematicStepper class into the kinematics/generic_cartesian.py, I see no harm in that.

That said, I donā€™t have a strong opinion on carriage_extra and carriage_dual vs extra_carriage and dual_carriage. I adopted the latter since this goes in line with [dual_carriage] for other kinematics, but if you feel strongly in favor of the first option, I can change the naming convention, so please let me know.

I skimmed through G-Codes documentation, and while NAME=x can mean very very different things depending on the command, I think in no instances will a user confuse it with [carriage x] reference. More importantly, [carriage abc] does not export its state anywhere and does not define any printer objects, so it is impossible to reference it anywhere except in very specific circumstances, where the reference to abc carriage is fairly clear (it can be referenced only in primary_carriage: abc and in kinematics string of a stepper, where x as a carriage and as an axis can be used interchangeably). So, my preference would be to keep [carriage x], [carriage y] and [carriage z], as this makes the configuration more concise in my opinion (no need to define axes for carriages, stepper kinematics for these carriages becomes clear and self-explanatory and generally encourages short and concise names for other extra carriages).

1 Like

This might be a little of topic but i cant figure it out (i decided to post it because it could be a bug)
Im impressed by this new kinematics, this is going to benefit a whole lot of people.
Including me, im building a parralel axis tripteron printer and for the kinematics i think i can use this. The only thing is that im unable to set it up. Whatever i try i cant get it to work without defining 4 carriages. Then i get the error of a missing pin of some sort becauese my mainboard has only 3 endstop pins but then i know the kinematics loaded correctly. I have my klippy.log file attached.
klippy (2).log (3.8 KB)

Edit: The bugfix has done its job for me. Awesome, keep up the goodwork!

Indeed it was a bug for non-idex setups, I pushed a fix, thanks for reporting! I suppose you can try to define your printer in terms of this new generic_cartesian kinematics (using 3 regular carriages [carriage x], [carriage y] and [carriage z], even though the printer technically doesnā€™t have such carriages, and with the appropriate stepper kinematics), just I think you may need to work carefully the homing procedure through, and likely kinematics limits calculations will not be able to prevent the moves invalid in this kinematics. But otherwise it should work, I suppose?

Thanks for working on this, and thanks for sharing your ideas and thoughts about it here on the forum.

I like the idea of a generic kinematic abstraction layer, and I would like to contribute a use case that is quite offbeat, but still something I would like to see supported by a change you are supposed to make:

I am using a modified CoreXYUV setup with a toolhead. This toolhead uses the UV part of the kinematics to remotely drive the extruder on the toolhead and also to support the movement of the Y-axis, as itā€™s mostly the bottleneck of my motion system. The modification is a closed-loop UV belt that drives a pulley that replaces the motor on the direct drive extruder.

I use this hybrid CoreXYUV setup on my printers as an alternative to a traditional 4wd CoreXY.

Would it be possible to add the extruder to the new generic cartesian stepper mapping?

Iā€™m not sure I was able to fully picture the setup (the ā€˜supportā€™ of Y axis movement by UV, I suppose, while also driving the extruder).

However, Iā€™m afraid this is not something that can be easily implemented today. There are two primary challenges with that. One is that E axis movement needs different treatment than X, Y and Z, i.e. Pressure Advance application. Theoretically, X and Y may have their own treatment, Input Shaping, so this one would require some work, but probably could be done. The second problem is that internal C code in Klipper supports only 3 axes at the moment (and so the extruder movements are modeled there as dedicated queue of moves with X==E, Y==0, Z==0). Reworking that is, probably, possible, but would require substantial amount of work and changes across the codebase. Given that this (future) PR is already large, I am not likely to work on that kind of change as a part of this work. Perhaps later in the future, not sure.

Updated the generic_cartesian configuration such that [stepper ...] sections should be defined instead of [kinematic_stepper ...]. Didnā€™t update dual_carriage and extra_carriage yet.

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.