I think it would be cool to make a robot that consumes a digital image and outputs a watercolor painting, and this project was the first step along that path. At its core, it’s a liquid handling robot that is a lot like those that are commonly used for biotechnology applications. The “canvas” here is a standard medical grade 384 well microplate. Instead of dispensing useful biological or chemical agents into the wells, my robot dispenses water mixed with a bit of CMYK printer dye and surfactant (more on that later). My script takes an image, and the robot re-creates that image by mixing dyes in a microwell plate.

I could have purchased an off-the-shelf 3D printer gantry and modified it for this project, but where’s the fun in that? Besides, for liquid handling applications, it’s important that the microplate be stationary, and most 3D printers have a moving print plane. I did all of the design work as a multi-body part in Solidworks.


The gantry that I came up with is straightforward. The Y axis consists of two parallel linear rails driven by a lead screw. The X axis consists of one beefier linear rail driven by a lead screw. For the Z axis I used, as you might have guessed, two parallel linear rails and a lead screw. The main plate is waterjet from eighth-inch aluminum, and the extruder mounting plate is cut from eighth-inch steel. 

Having a 3D printer when designing and building a gantry like this feels kind of like cheating. For most of these components, I didn’t have to worry about DFM practices and the likes. A word of caution though: if you’re going to 3D print frame components do not make them locating features. I printed many of the components for this build on a Form 3 SLA 3D printer, but even with the high accuracy of SLA relative to other printing processes, the accuracy isn’t quite there for locating components like motors and rails.

If you do choose to assembly-locate critical components with structurally-rigid 3D prints, you’re likely to end up in a situation where your rails and lead screws fight against one another. I encourage you to only use 3D printers for very rough alignment, and then dial in your components either experimentally or with proper metrology equipment later. A belt driven system is a lot more forgiving in this area because belts are plenty compliant on their own, but rigidly locating with printed parts is still poor practice.

There were a few parts on this system that I wasn’t comfortable printing because they represent large-ish moment arms with modest loads at the extents. In those cases, I used a combination of waterjet machining with post-machining on a bridgeport, and CNC machining on my Shapeoko. I was pleasantly surprised that my little Shapeoko was able to get through 6061 aluminum. A bit of cutting fluid and occasional pauses to let the tool cool down went a long way. 

Beyond that, there are a few laser cut pieces, a few FDM printed components, and an SLS printed component. SLS was entirely unnecessary for the mentioned part, but I was curious to see how the process would hold up for a medium-sized, thin-walled part.



    As far as motors go, this build uses three stepper motors for axis driving, one stepper motor for extrusion, and five servo motors. The common reprap configuration of an Arduino Mega 2560 with a ramps stepper shield seemed like a good candidate for this build, but it has one issue in that there aren’t enough pulse-width control output servo pins for all of my motors. To solve this, I added a PCA9685, which is an off the shelf servo expander chip. The PCA9685  communicates via the I2C bus standard and so it only requires the SDA and SCL pins from the Arduino. Fortunately for me, neither of those pins are used for the Ramps 1.4.

I could have used GRBL for programming the controller, but I’m more familiar with Marlin, and I knew that it was going to do most of what I needed out of the box. The one challenge was getting it to communicate with the PCA9685. 

    In Marlin_main.cpp, I was able to import Adafruit’s PCA9685 library and add initialization code. From there, I created a custom gcode command “M954”  to control the servos. M954 takes 2 arguments, a string and a float. The parser looks for the letters “C, M, Y, K, W” and a number that represents the pulse length. For example, “M954 C150” causes the servo that controls the Cyan color channel to open.

    I’m using cheap hobby servos which aren’t known for their accuracy, so I tuned the open and closed positions of the valves experimentally. I found the valves on mcmaster - they’re very simple two-state stopcock valves. I T-slotted the top of each valve and printed an adapter to interface them with the splined servo shaft. This valve construction worked very well; I haven’t seen it done like this elsewhere on the web, but I would encourage anyone who wants to make cheap, quick, reliable valves to try it out.

The liquid handling part of this is fairly straightforward. If I want to dispense Cyan pigment, a gcode command opens the Cyan valve and ensures that all of the others are closed. To change to Magenta, the Cyan valve is closed and the Magenta valve opens. The Cyan is purged into a hidden waste vessel beneath the frame, and the Magenta is loaded. 

The script for this is written in Python and takes a 24 x 16 pixel (the resolution of the well plate) image as an input. Currently, I’m processing the images in photoshop to set the proper aspect ratio and resolution. I could set both of those in the script, but then I would have less control over how the image is cropped. The script iterates through each pixel and determines the RGB value. The RGB value is converted into 5 values, Cyan, Magenta, Yellow, “K” which represents toner or Black, and Water. 

CMYK is preferable to RGB for actual print applications. RGB is useful for displays because if you max out the intensities of each color channel you get white. In real life, mixing RGB colors together doesn’t quite yield the same result. CMYK assumes that you’re starting with a white background and then adds colors to make it less white. A value of 20% for each of the CMYK values makes something 20% less white and as such the result would be a light grey.



I’m using CMYK and “W” (water) because I want to fill each well plate to around the same volume. The water makes up differences in volume for each cell. This has a mild effect on the color, but the final result looks nicer when all of the well plates are approximately the same height. 

Once each pixel is converted to CYMKW, the actual extrusion length for each color is calculated. The relative amounts of each color are determined and made to sum to 3.5 (more on this later). The extrusion lengths for each color are then written to a dictionary which uses the pixel number from 0 to 383 and the color as keys. 

The rest of the script writes gcode to a file. It gets extrusion lengths for each color from the dictionary, and it does straightforward math to iterate between each cell. It also automates the purge process between color changes. If you’re interested in the code, you can find it here.

I know that my well plates are 120ul, so the math for calculating the extrusion length is as follows:

120ul = 120 mm^3 
120mm^3 = π(1mm^2)(x) # solve for cylinder height to yield 120mm^3 given tube ID = 2mm
X = 38.2mm
Diameter of tube path = 36.5mm
Circumference of tube = π(36.5)
Circumference of tube = 114.67mm
38.2mm / 114.67mm = 0.333 # solve for rotations to displace 38.2mm of tube
16 microsteps * 200 steps per revolution = 3200 microsteps per revolution 
3200 microsteps per revolution * 0.333 revolutions = 1067 microsteps 
1067 microsteps / 500 steps per unit = 2.1340 units

Now that the math is done, I can tell you that 2.134 units of extrusion does not displace 120ul of water. The output flow rate of a peristaltic pump is sinusoidal. This can theoretically be modeled, and you’ll find research papers that have modeled it, but I experimentally determined that 3.5 units of extrusion results in a full microplate well.

One major challenge with this project was creating small and consistent droplets. The smaller the droplet, the better the fidelity of the colors in each well. I had three things going against me: each microplate well has a volume of 120ul, peristaltic pumps are not consistent in their output, and water has surface energy. It’s worth mentioning that this issue with droplet size is also a challenge among inkjet 2D printing processes. 2D printers tend to solve this problem either by heating the ink thus causing it to expand or by using piezoelectric oscillators to propel the droplets out of the nozzle. 

I can’t do much about the first issue. I chose the 384 cell well plates so that I could get higher resolution images than the typical 96 cell plates, but of course that meant that each well was considerably smaller. The second issue is fundamental to peristaltic pumps, so I can’t do much for that either. 


Liquid handling workstations used for biotechnology often use an array of linearly actuated syringes to get around this problem. If you reduce the volume inside a syringe by 1uL, pretty damn close to 1uL of fluid tends to be dispensed, and you can’t get much more accurate than that, once you compensate for static friction of the plunger. In my case, I didn’t want to have to reload syringes - I found the open containers of color much easier to refill.

The third issue regarding the surface energy of water was a factor that I was able to do something about. The surface energy of a material at a basic level refers to how physically attracted it is to other materials. Water droplets tend to stick to themselves and surrounding surfaces. If you’ve ever looked outside a car window during a storm, you’ve probably noticed that droplets coalesce to a certain size before falling. At some point, the force of gravity exceeds the attractive force due to surface energy, and the droplet falls. This phenomenon also underlies properties of fluids like capillary action. 

In my case, the water droplets were attracted to the tip of the syringe. This meant that the droplets had to grow large before they would fall into the proper well. Surfactants reduce the surface energy of water, usually by a factor of 2 or 3. Adding a small amount of surfactant to my dye solutions meant that the droplets would fall from the syringe at a much smaller volume. This allowed me to mix more, smaller droplets into each microplate well resulting in better color fidelity. 

That sums up this project pretty well. This project took a little over 3 weeks, with most progress happening at night after finishing my day job. Now I have a versatile liquid handling platform to play with. I’m planning on upgrading and using this thing as a full-fledged watercolor robot. I had a lot of fun building this, and hopefully you learned something along the way as well.

Next
Next

Sisyphish - A Kinetic Art Fish Tank [2020]