Support for additional G1 axes (6-axis support)
This PR adds support for extending G1 commands with additional axes. For example, one could issue a command like G1 X10 Y20 Z30 E40 A5 B6 to move many motors simultaneously.
This is sometimes referred to as "6-axis" support, although in this implementation the only limit on the number of axes one may register is available letters in the alphabet.
This support works by extending [manual_stepper my_stepper] objects to include a new MANUAL_STEPPER STEPPER=my_stepper GCODE_AXIS=A type command. (See the docs in this PR for the usage.)
In order to make this change I have altered the toolhead.get_position() class to support returning lists of more than 4 axes (it used to return x,y,z,e) and similarly toolhead.move() may require more than 4 axes. Thus, a big part of this change is identifying and updating the many internal modules to support that. I've attempted to find and address these issues, but lots more testing will be needed.
FYI, @dmbutyugin , @Arksine - I've modified some modules that you maintain as part of this work.
In addition to the basic support there are additional upcoming changes I plan to look at:
- Updating
G92to support setting G-Code offsets for additional axes. - Adding axis limit checks to manual_stepper objects.
- Possibly improving the homing speed of manual_stepper objects.
- Adding support for placing limits on instantaneous velocity changes, maximum acceleration, and maximum velocity on extra axis movements.
- Support for basic "tool center point control" - a mechanism to keep the toolhead in contact with a print if that print is rotated by an A/B/C motor that spins the bed.
My thinking is that the above changes could be submitted as additional PRs.
All of this work would be targeted at a merge after v0.13.0 is tagged.
Cheers, -Kevin
And a few more generic thoughts on the matter.
First, for generic_cartesian, I think it would make a lot of sense to allow mapping of dual_carriage as extra axes. This could be done explicitly with a command like SET_DUAL_CARRIAGE CARRIAGE=u MODE=INDEPENDENT (or EXTRA_AXIS, or something else). This would require making more complicated move validity checks, so I'd look into that once generic_cartesian kinematics and this PR is committed. But at least I want to make it happen longer term, as I think it could be really useful for some features like coordinated toolhead swaps, or on CoreXYUV where the two toolheads could, arguably, print two independent parts (though that would also require supporting more than 1 extruder axis, though this is also something doable).
Second, as I mentioned elsewhere, it would also make sense for generic_cartesian to support
[manual_carriage m]
...
while this would be less flexible as [manual_stepper], it would provide a convenient way to define various limits, such as maximum acceleration, junction velocity, etc. This is something that is mentioned as a TODO in the current PR, but I think for manual_stepper the configuration of such options would be a bit clumsy, as currently there is no fitting API for this kind of config (though perhaps it can be integrated in into the [manual_stepper] section itself - but then this would also fit the general logic of generic_cartesian to split stepper sections into [carriage] and [stepper] sections). And another benefit of this approach for generic cartesian is that we could support something like
[stepper extra_stepper]
carriages: x-m
Again, this would require extra work and would have to wait until generic cartesian and this PR is merged. But I just wanted to highlight the ideas I had related to this PR.
First, for generic_cartesian, I think it would make a lot of sense to allow mapping of dual_carriage as extra axes. This could be done explicitly with a command like SET_DUAL_CARRIAGE CARRIAGE=u MODE=INDEPENDENT (or EXTRA_AXIS, or something else).
That's interesting and I hadn't really thought of it.
For what it is worth, I'm not sure that would be that useful without also having separate lookahead queues. That is, I can't see a way to drive two separate print heads simultaneously without segmenting every move to some kind of common denominator between all moves. Even with separate lookahead queues it would seem very challenging.
Another possibility is to go the other way - allow users to define [manual_stepper mystep] sections and then at runtime assign them to a "carriage". Then users could associate and disassociate as needed.
This is something that is mentioned as a TODO in the current PR, but I think for manual_stepper the configuration of such options would be a bit clumsy, as currently there is no fitting API for this kind of config (though perhaps it can be integrated in into the [manual_stepper] section itself
My plan is to allow users to specify those parameters at run-time - for example something like: MANUAL_STEPPER STEPPER=mystep GCODE_AXIS=R MAX_INSTANTANEOUS_VELOCITY=2
My current thinking is that manual_stepper sections focus on run-time configuration and have little static configuration.
There are pros and cons to that - there's less structure to the setup macros, but it allows more user customization. For "niche" setups doing configuration at runtime can be advantageous.
Cheers, -Kevin
Excellent attempt. https://github.com/gear2nd-droid/klipper Here is the 6-axis modification I am working on. I hope you will find it useful.
One unique feature is that I have added calc_move_distance(self, start_pos, end_pos) to each Kinematics (only corexybc.py has it implemented currently) and call it in init of class Move. This allows us to calculate the distance moved at the nozzle tip point, depending on the kinematics.
Also, in init of Move, we limit the movement speed for each axis. For this purpose, the following processing is performed on each mechanism and parameters are added to the printer section. self.max_speed_x = config.getfloat('max_speed_x') self.max_speed_y = config.getfloat('max_speed_y') self.max_speed_z = config.getfloat('max_speed_z') self.max_speed_a = config.getfloat('max_speed_a') self.max_speed_b = config.getfloat('max_speed_b') self.max_speed_c = config.getfloat('max_speed_c')
I also have two environments (6-axis parallel link mechanism and corexybc) that I can test on actual equipment, so please contact me.
Here is the 6-axis modification I am working on.
Thanks for reaching out.
It seems the implementation here is a little different - there are no changes to the underlying kinematics. The additional axes are synchronized with the main toolhead movement, much in the way that an extruder is. That has some advantages and disadvantages - it simplifies the code as it doesn't require changing the low-level kinematic implementations, but it may make it harder to implement advanced kinematics where B/C motors are tied in some way to X/Y/Z motors.
-Kevin
As you are aware, the processing of the F value will be wrong for movements involving rotational motion, such as the ABC axis. As a countermeasure, it is necessary to calculate the F value so that the relative velocity of the nozzle tip with respect to the origin of the modeling bed is the F value. One way to do this is to require each kinematics to have the ability to calculate the distance traveled between moves. For a typical 3-axis, the square root of the square of the difference between XYZ is sufficient. If rotation is involved, kinematics is needed. It is computationally heavy, but we have confirmed on actual equipment that it works with raspberryPi3B+ with plenty of time to spare. Please could you add this feature as well? Probably, it is also necessary for tool tip endpoint control, etc.
Thanks all for the feedback! I have committed this PR. I agree there are further enhancements that could be made, but I think they can added on top of this PR.
-Kevin