Playing with CadQuery
In the last post, I mentioned I was having reservations about OpenScad, and its suitability as CAD software for KIWI. The primary source of these reservations was simply that after making only minor headway, each operation had become so tedious. Simple things like a bracket for a servo was becoming far too much trial and error for my liking. Initially, I thought I’d be able to simply take some measurements of my part before I begin modelling, save them as variables “servo_width, servo_height” or something similar, then use these dimensions to define our extrusions/cuts for the servo mounts.
This idea breaks down pretty quickly in practice, however, as you realise there just isn’t a good way to define relative coordinates. You can’t, for example, select the face that was just extruded, and extrude 10mm from that. Everything (As far as I know) is defined in absolute world coordinates, and this makes /assemblies/ an absolute nightmare. As a result, everything was taking orders of magnitude longer than it would have taken me in solidworks, and the results were awful (Although I suspect that was do to with my own incompetence, there are some awesome OpenSCAD models out there).
Checking the competition
After a few weeks of wrestling and wranglin’, I decided to do some hunting and see what other options I had available to me. It didn’t take long to find CadQuery. It’s a bit of a goofy name, but I was immediately intrigued by the some of the demo images of cq-editor, it looked modern and powerful. The installation was a bit rough, as the CQ team seems to rely pretty heavily on Conda, as opposed to using Pip for their installation and package management. I personally prefer to use either pip, or install from source. Installing from source seemed worked well enough for KIWI’s Docker container. It didn’t take too long to get my head around the ins and outs of CQ, (With a bit of help from the CQ Discord). After a few days of messing around, I came to the conclusion that CQ was a better solution for what I was aiming to achieve with KIWI than openSCAD.
Advantages over OpenScad
- Workplanes and Selectors Two massive features (As far as I’m concerned) of CQ is the ability to tag and select features (edges, faces, vertices etc) of the model /after/ they’ve been constructed. For example, you could extrude out a face, then select the outer most face using .Faces.selector(">Z"). With this, you can either tag it for later use, or draw a workplane directly onto the face. This makes things like bezels and chamfers an absolute breeze.
- Relative positioning As a result of being able to select features, you have the ability to model using the familiar process of “Select face, draw a sketch and extrude/cut/loft”, which is definitely my preferred method of modelling.
- Written in python, and thus model in python Cadquery is a set of libraries for python, which allows for LSP powered auto completion and debugging. All of your modelling is in python, which allows you
- cadquery-server, 3djs and performance benefits As far as I can tell, the two best options for visualising your models as you code are cq-editor, and cq-server. I started with using cq-editor, but began running into performance issues with my imported 8318 STL. I tried out cq-server on a whim after seeing it posted on the Discord, and the performance was orders of magnitude better, and as a result cq-server has become my visualiser of choice.
- (Proper) Assemblies, with the ability to fix/constrain parts to a parent In comparison to OpenScad, CQ is in a different league when it comes to assemblies. Through the usage of Casadi (Insert link) as a numerical solver, you’re able to simply import a shape and place them using mates, very similar to how one would create an assembly in Solidworks. This includes things such as placing a screw inside a hole using concentric constraints. This can be a bit confusing at times, and I did struggle at a few points when the model wouldn’t do what I was wanting, but it works well once you figure it out. You can sort of do it in Openscad with the Relativity package, but it’s definitely not as powerful as in CQ.
I’m still nowhere near as fast to mock up parts in CQ as I am in Solidworks, but I’m enjoying the software, and it seems like it will be suitable for what I’d like to achieve with KIWI.
Driving joints in gazebo.
After getting the new model into gazebo, I also wanted to get kiwi moving in simulation. I quickly inserted ros2_gazebo_control into the xacro, as well as added an effort controller to a controllers.yaml. When I was starting out with ROS, I found the Gazebo/control interfaces to be confusing, and it took me quite a bit of digging to figure out what was happening under the hood. I wanted to see if I could represent these systems graphically in a meaningful way, which would help others trying to figure this stuff out.
After some toying, the best approach I found was to create some diagrams in PlantUML showing the inheritance heirarchies. While doing some research, I came across Articulated Robotics, which not only has a fantastic name, but also goes over their process for creating a robot in ROS2. They have some great diagrams going over the hardware interface and the controllers, but they don’t go into as much detail as I think is required to get a proper understanding of how these interfaces actually end up interacting.
Ros2 gazebo interface
I’ve used the ros2_gazebo interfaces once before, and I never really dove into the code to figure out how it all works. It’s not nearly as complicated as my first impression led me to believe. Writing your own gazebo interface is analogous to writing a custom hardware interface, in that it doesn’t seem to be too difficult.
The gazebo interface begins its inheritance from the SystemInterface. This aligns with what we would expect from an interface to ros2_control. The same is true for other interface types as well, actuator, sensor and hardware interfaces all share the same SystemInterface Base class. We can see that the system interfaces introduces virtual methods for exporting our command and state interfaces, as well as the read and write methods. Building on this, the GazeboSystemInterface introduces the initSim method, which is also required for inheritance. The initSim method is for any reads/writes to and from the simulation that need to happen prior to the initialisation of the interface.
And finally, if we view our final GazeboSystemInterface, we can see it implements an initSim method, where it registers pointers to the gazebo joints. From there, these points are used for the on_init register_state/command_interfaces as well as the read/write functions.
Each class should have a hyperlink to its code on GitHub, if you’re wanting to check the source code for how it works yourself.
Ros2 control
Now we have the gazebo interface, we can analyse the effort_controller in the same way, although this one is slightly more complicated. Once again, you can click the class to go to the source code on GitHub.
At the root of the inheritance we have the ControllerInterfaceBase, which does a few interesting things, namely assigning the command/state interfaces and overwriting the standard LifeCycleNode interfaces. The ControllerInterface inherits from this base, and while it doesn’t do anything of note, it does have a partner “ChainableControllerInterface”. This, as you may suspect, is an interface for chain-able controllers.
From this, we move away from ros2_control, and into ros2_controllers. Our forward controller interface does the rest of the heavy lifting, creating the subscription to the commands topic and creating our update loop which reads these commands, then creates joint commands from them. The next two classes down, ForwardCommandController and JointGroupEffortController are oriented around the command type. JointGroupEffotController defines the command type as “EFFORT”, and the ForwardCommandController initialises the command handle as this type once defined.
Spin to win!
In the coming posts for kiwi, I’ll be writing a simple feedback controller for KIWI. Beyond that, there should also be some discussion of the hardware being used, the simulation/reality gap, and strategies for helping to bridge the two.