• Login
  • Register

Work for a Member company and need a Member Portal account? Register here with your company email address.

Project

The CNC Seal Carver

Lingdong Huang

A CNC machine built from scratch for milling Chinese seals and software for computationally generated seal designs.

Chinese seals (stamps) are traditionally used as a form of signature on documents or works of art. They are often carved out of stone, and relief-printed with red ink. While they're initially made for utilitarian purposes, the design and carving of seals had grown into a unique art form, with many artists specializing in it throughout the history of China.

I built, from scratch, the hardware, firmware and software of a CNC (computer numeral control) machine, to carve Chinese seals automatically. The time and labour of manually carving a seal is greatly decreased, and the impurities of the stone stock has a much smaller detrimental effect on faithfully reproducing the design. The custom software also allows for computational design of seals -- the using of algorithms to generate patterns from simple inputs.

CAD Modeling

I used the onshape software to CAD (computer aided design) the machine. This is my first time using a GUI-based CAD tool made by others, instead of generating parts with my code, a process you can read about here. Thankfully, after an evening of learning the key tricks of CAD from Quincy Kuang, a professional box-maker from Tangible Media Group, I was able to finish designing the first version of the machine on the next day.

I find the interface of onshape reasonably intuitive. The workflow mostly involves sketching, extruding sketches, sketching on extrusions, extruding sketches on extrusions, etc. The coolest feature is that you can "go back in history" and change something, and the present model will reflect those changes. However, sometimes it gets super confused if your change changed too much topology. I also found it a chore to make everything 100% parametric, (or "DRY", in programming jargon) -- sometimes remembering some "magic numbers" and entering them a few times is just easier. Overall, once you understand the pitfalls of the tool, they can be avoided and you'll end up having a good time modeling the stuff you want to model.

Assembly

When I put together the first 3D prints, most of them did not quite fit into each other. Therefore, I fixed the errors in design and printed again, and they started fitting better.

I chopped the aluminum extrusions known as "8020" into pieces with the chop-saw at Mars Lab. Then, I used these little pieces of sliding things with a screw hole which I call "little fishes", as well as those "L"-shaped brackets to hold the extrusions together. Initially, the frame was see-sawing on the table. Then, I loosened some of the screws to make the frame "relax" a bit so that it is no longer see-sawing on the table. Then I tightened the screws again, which seemed to fix the problem.

I bought most of my components on amazon, each of the cheapest variety. Interestingly, the linear rails turned out much beefier than I imagined (though they're still poorly made). I designed my machine around these oversized rails, and end up liking the aesthetics of it. It gives one a sense of both sturdiness and compactness.

Z-Axis

I did not use the aforementioned overweight linear rails for the z-axis, for I feared that they were too heavy to lift with a meager NEMA-17 stepper motor. Therefore, I sort of designed my own "poor-person's rails" made out of 3D printed plastic, a jigsaw-puzzle-like idea involving two parts locked into each other in other directions but sliding only along the z-axis.

The first, swallow-tail-shaped design had a "sloppiness" problem, as it allows the moving-part to tilt an amount which is unnecessarily large in proportion to the gap between two parts (which is mandatory for smooth sliding). Therefore, I improved the shape of the locking bits, into this hammerhead-shark-head-like shape which still allows 3D-printing without support, while having a smaller leeway for tilting given a certain tolerance.

After applying copious amount of grease onto these printed rails, they slid very smoothly, while being tightly held together without sloppiness.

I also played with a different design to see if that would work even better, which involves tiny metal balls and metal rods. I basically made some tiny bearings of my own. The tolerances took a lot of prints to get right, but it end up working pretty well. However, it wasn't a big improvement over the jigsaw approach (if any at all). So I stuck with the previous design, as it was much simpler.

Clamp

I designed the clamp after the sort of clamps that were traditionally used for seal-carving. The triangular concavities on either side allows fitting an arbitrarily-sized, suqare-shaped seal into them by tightening a big screw.

The clamp is its own module separate from the bed, making it easy to repurpose the machine for non-square shaped seals or something else entirely.

I used a big M8 screw. I designed a "pocket" for a nut inside the wall. I printed some "wings" for the head of the screw, so that it can be more easily fastened by hand.

The second version rotated the clamp by 45°, such that the seal would be axis aligned, making toolpath generation more straightforward (and everything more intuitive overall).

Steppers

This was my first time using stepper motors. I've seen them everywhere: printers, plotters, other people's machines, etc., so I was excited to start using some. I got my hands on a few A4988 stepper drivers, read a random tutorial online, and put together the circuit on a breadboard with some jumpers. I tested with a spare stepper separate from the ones already installed on the machine.

Unfortunately, it ended up frying an Arduino Uno, as well as taking a USB-C hub with it. Luckily, my laptop's port appeared unharmed.

I realized the problem was probably with the power supply. I used a single power adapter which goes to the Uno's barrel jack, and connected the stepper driver's VIN pin to the Uno's VIN pin. This way the logic and the power share one initial source, and I'll only need one power supply. Somehow the 5V regulator on the Uno didn't like that and decided to snuff it. Which is strange because the voltage was within specs.

I convinced myself that perhaps whoever made the knockoff Uno was using a crap regulator, or that my $10 power adapter was crap. I removed both from my equation by getting a new adapter, and one of those big, standalone, surface mount regulators.

Later I found out that I was wrong (after burning a few more microcontrollers to a crisp). But for the time being, as the fiery catastrophes didn't happen consistently, I was able to figure out the software component for driving a stepper driver:

Basically there're two pins, direction and step. You flip the direction pin to indicate which direction you're going, and pulse the step pin to go one step. Very straightforward. However, the stepper was making a horrendous noise when I asked it to change from one direction to the other. It only happens with one of the directions, and with the initial start up. It did not happen when changing from the other direction, and the asymmetry confused me.

After trying a whole bunch of things I realized that the solution was as simple as adding a delay before turning directions and starting up, duh. Steppers simply find it difficult to turn around at a whim, and needed a few milliseconds to take a breath. The noise was their "scream in pain".

PCB

As I figured out the basics of taming a stepper, I decided that it was also time to make a custom board. However, I lack the patience for awaiting PCB's to arrive from Chinese factories.

So I made one with a "perf-board" and a bunch of wires (and my battle-tested "flagship" dev-board featuring a SAMD21E microcontroller). I've only tried this technique once before. It is certainly an interesting experience, though it is also somewhat a pain. Usually making a PCB involves a few steps: designing the schematic, routing the traces, fabricating the board. With a perf-board, you're sort of doing all three at the same time. It certainly produces this unique look, but I really doubt if it can be debugged at all, being a tightly wound tangle of wires.

It took more time and effort to make a board this way, compared to designing one with KiCAD and sending it off to a Chinese factory -- if the week-long waiting time is not taken into consideration for the latter.

More Horrendous Noises and Fiery Death

As I installed the circuits onto the machine, more problems reared. First, it was very noisy, like the steppers were in excruciating pain (again).

It turned out that in my design, all steppers are flush against walls, either 3D printed ones or ones made of aluminum structs. I thought it would make them more steady. However, the vibrations from stepping was banging them against those walls over and over, thus making those painful noises.

I then looked at all those printers, plotters, and other people's machines, and it seemed like their steppers are always held in mid-air without touching any walls besides the ones that they're fastened to. Huh.

Though they were quite loud, the noises weren't the biggest problem. I joked that it's like those fancy cars where loud engine noises indicate luxury and status.

The bigger problem was that my microcontrollers and regulators were still frying from time to time despite the new power adapter and wider operating range of the regulators.

I finally gave up and used separate power supplies for the power and the logic. I was simply running out of microcontrollers to fry. I would later deduce that the steppers were probably sending some sort of backward spike to fry the source that fed it. Perhaps some diodes could help in this case, but I was too traumatized to take any more risks.

Firmware & Software

I didn't use a readymade CNC kit, I didn't use a readymade CNC board, so I certainly wasn't going to use any firmware or software written by someone else.

I wrote a 3D version of Bresenham's line-drawing algorithm to control all three axis simultaneously. USB serial was used to transfer commands from my laptop to the microcontroller.

I had a choice of using gcode as the language of the commands. But I figured that it was probably an overkill to parse it according to spec, just to be able to read all those commands that I probably won't be using anyways. So I came up with a language of my own, which is really really simple. A command looks like this:

l10,-20,30

The first `l` says it's a linear move, then the relative amount (in steps) in all three axes follow immediately separated by commas. Commands are separated by newline characters (`\n`, or 0xA).
Currently this is the only command. There is no absolute move, only relative move. (The firmware doesn't even have a notion of "home").

The spindle is controlled by a physical toggle switch, completely separated from the rest of the circuit. Before you start milling, you just manually flip it on before issuing any commands, and manully flip it off afterwards. No need for a driver if you're just spinning forever!

On the software end, I have a python script for piping a file, line by line, into the serial port. Another script converts a binary image into tool paths.

The main algorithm is distance transform, which for every black pixel, figures out the distance from it to the nearest white pixel. Then, a few thresholding operations and contour tracing passes produce the necessary toolpaths to clear out all the black areas while keeping the white areas intact.

I decided to use a V-bit endmill, for it allows carving very fine details (if calibrated well enough), and is not easy to break (since stones are kinda stiff).

Wax

Chinese seals are usually carved on a type (or rather, a few types) of stones known as "soapstones" to westerners. However, these seem quite hard to come by here in the US. I found some on amazon, but they cost around 20 times as much as they do in China, are of the most inferior quality, and some come broken in the package.

I brought some "good" ones on my previous trip home to Shanghai, but they weren't many.

So for testing the machine, I set my eyes upon machinable wax as a cheap replacement for real stones. I had some fun with it during the *How to Make Almost Anything* class, where we machined the wax, so I know for sure that they can be machined.

A big block of wax cost me $20, but the hard part was cutting it into smaller blocks. I initally used a saw where the cutting part is made out of a wire. Then the wire broke in half. Then I used the remaining half of the wire. Then it broke again, and so on, until all the wires were shattered into tiny pieces, but I've only left a small dent on the wax.

The next day I bought a big beefy saw, fit for a murderer, at a nearby hardware store. I was able to cut the wax into smaller blocks, though it took quite some effort.

Initial Tests and Firmware Bug

Finally the exciting time has come, to try to mill the very first seal. 

The input was a scanned and processed image of a historical seal that I liked.

With horrendous noises from the spindle, the steppers, and the walls banging against the steppers, the machine was set into motion. For a fleeting moment it looked as if everything was going to work. Then it started inspiring less confidence as the bit seemed to be sinking deeper and deeper into the wax, and the pattern being made also didn't look exactly like what I had in mind.

I let it go on for a while but eventually the bit sank so deep that chips of wax were flying everywhere and it felt like some sort of disaster was on the horizon.

Abort! I rescued the mutilated piece of wax from the crazed maw of the rogue CNC, and came up with some theories.

First is that the stepper on the z-axis was not able to hold its ground once the spindle started spinning. The spindle was stealing too much juice, that there wasn't enough juice for the steppers, and the z-stepper was skipping over time under the weight of the spindle, causing the bit to sink deeper.
This was easily fixed by giving the spindle a separate power adapter.

The other problem was much more troublesome. It appeared that the motion on X-Y plane was drifting over time. I initially suspected that it was also skipping steps on those axes. But it didn't make much sense, as wax shouldn't prove too hard to mill.

After a few more trials, I narrowed down the issue, observing that it was only drifting when milling *certain* patterns. Moreover, the amount of drift was the same every time. Thus I milled one of the worst offending pattern by itself a few more times, to discover that the drift only happens when both X and Y axes are moving simultaneously (but not always).

To confirm my theory, I re-generated my design, eliminating all simultaneous X-Y motions, and resort to accumulating tiny rectilinear movements to simulate diagonal movements. This time, there was no drift.
Having been tortured by this problem for many hours, I was ready to accept this workaround. To do so, I had to move the 3D Bresenham's algorithm from the firmware to the software. This is when I finally realized the real problem:

In Bresenham's algorithm, depending on the slope of the line, there's always a driving axis, and the other axes merely follow along. To save myself from copying the same code 3 times for each case, I indulged in the sin of writing some crazy C macros. In doing so, I failed to realize that instead of connecting the cases with `else if`, I connected them with mere `if`'s. This causes a problem if and only if the amount to be moved on two of the major axes are exactly the same.

The special case was rare enough that I didn't catch it with the initial testing right after implementing the algorithm, and subtle enough that I wasn't able to realize what it is when looking at the milled output.
I was so close to accepting the workaround! It felt very good that the real problem was found and fixed once and for all.

Interestingly, designing and building the mechanical parts of the machine, something I was trying for the very first time, only took me three days, while fixing circuit and firmware problems, something I've been confidently doing for quite a while, nearly took me a week.

First Success and Second Iteration

Finally, with all the annoying little hardware and firmware bugs fixed, the real first, successful sealed was brought to be. I inked it with traditional red ink pad, and stamped it on rice paper. It looked good.

However there was still much to be done. I quickly CAD'ed a second iteration. All the steppers were now separated from adjacent walls by two millimeters, to address the noise issue. I also added a second linear rail to the X axis. While it was working fine with only one before, I thought that the design was putting some unnecessary stress on the stepper's lead screw with a force in its perpendicular direction.

With these changes, the machine looks even beefier. "Beefier than beefy." I was thinking that perhaps I could name it "extra beef", after the restaurant menu option.

New PCB

To match the new looks, I designed a new control board, this time properly with KiCAD.

For each of the three stepper drivers, a corresponding Chinese poem is printed on the back-side silkscreen. The poems all come from *The Book of Poems*, the earliest collection of Chinese poems. The X-axis poem mentions the characters "left" and "right", the Y-axis poem mentions the characters "back" and "fro", and the Z-axis one, "up" and "down".

I also illustrated the silkscreen with a line-drawing of the first version of the machine.
When the new PCB's finally arrived, I designed a special enclosure for it such that it can be flipped open, showing off the poems on the back of the PCB.

However, there was one problem yet. On the PCB I mistakenly connected the ground of the spindle to the ground of the rest of the circuit, and it turns out, whenever I turn off the spindle, it will introduce a temporary "black-out" causing all the other components to reset, presumably the spindle suddenly draws a lot of power and robbed the juice out of its colleagues.

In my defense, when drawing the schematic, it is very easy to assign "GND" to all the things that are ground, and when routing the tracks, it's very easy to just connect everything the software tells you to connect, hence the mistake of merging two grounds.

But it is but a minor annoyance, since the time when you need to turn off the spindle, is the time you FINISHED milling, so you wouldn't mind whether the board is resetting anyways.

3D Printed Dust Shoe

As I improved the machine and got more and more confident with its capabilities, I started milling on real stones. As they're more precious compared to wax, I would grind away a failed trial with a big endmill, to recycle the stock for the next. I also do this pass to make sure the surface is absolutely flat, which is essential for a V-bit to reproduce an exact pattern with no uneven thickness.

However, the process gathered a dust storm in the room. As the spindle spew out large amount of powdery remains of removed substance, everything around is covered in a fine white mist. There's dust on the screws, on the steppers, on the spindle, on the PCB, on the desk, on the floor, on my face and on my hands, what a mess! Must have breathed in some too! I felt like a slave working in a Roman quarry. Those guys sniffed enough of it but didn't snuff it (not right away anyways), so I should be fine, right?
I decided that one of those "dust shoe" things one often find on CNC machines would be helpful. But how to make one? Could it be 3D-printed? The famous "hairy lion" and "hairy Einstein" prints came to my mind, and I realized the same technique could be applied here.

Therefore I generated an STL file (with some code of course) that consists of two blocks separated by some distance, and connected in the middle by thousands of thin strands of 0.2mm by 0.2mm in thickness. The printer would then print these strands as "overhangs". Finally, I cut off one of the blocks with an exacto knife, and obtained this giant tooth brush thing.

I printed quite a few versions with different printers and filaments -- some filaments tend to break off very easily, and some are too stiff. A partially clogged nozzle managed to print a dreadlock version. I also tried to print a red one to resemble tassels often used as traditional Chinese decorations -- but decided that it looked over the top. Finally, I went with one printed with hatchbox black and installed it onto the machine.

I think it worked by trapping most of the dust. Of course, if you fiddle with the bristles afterwards, the dust will still fly out, so I guess don't do that.

Differential Un-un-growth

My advisor Zach suggested that I use some reaction-diffusion or differential growth to computationally generate seals. I think the aesthetic of these algorithms does resemble that of some of the classical schools of seals. Therefore I incorporated a little physics engine for doing that into my software pipeline.

I am quite aware of the algorithm of differential growth and have played with it in the past, but it was only when I started writing one from scratch this time, that I realized that I've already written one from scratch the previous time, without knowing that I've just written one from scratch. Well, sort of.

In my master's thesis, I introduced a physics simulation that makes my graph-based programming language more compact (in the literal sense, i.e. taking up less space on screen or paper). The idea is that all the lines are turned into point masses linked by springs. The system trys to shrink driven by a centripetal force, and in the process, if a node is too close to its neighbors, it gets "subsumed". Which is the exact opposite of differential growth, where if two neighbors grow too far apart, they give birth to a new node. So perhaps I should call my algorithm "differential un-growth", or "differential collapse".
Therefore, it was not difficult to take my old system, and reverse a tiny bit of logic, and obtain the more traditional differential un-un-growth.

I also played with the algorithm a bit: The "folds" or "wiggles" in the linework of many traditional seals tend to follow rectilinear grids, whereas in differential growth it is more free to grow anywhere. Having the convenience of a physics-simulation-based algorithm, I merely added some new forces attracting the nodes to grid lines.

It was quite helpful, but still the output doesn't fully resemble the style of those seals I was trying to replicate. I think this is because we humans tend to generate decorative patterns in a more intentional and more symmetric way, and with an understanding of the underlying glyphs; whereas the algorithm merely grows it in random ways, quickly diminishing in readability as it adds the wiggles in unfortunate places.

To reproduce that look more faithfully, I think there're still several heuristics I can try. Or perhaps just feed it to AI.

Nevertheless I think it is an interesting new style with its own unique aesthetic -- even in classical China they come up with new seal styles every once in a while, so why can't I start one with my little algorithms? 

Bluetooth Connection

On my new PCB I was using a ESP32-C3 microcontroller as the "brain" of the circuit. These days I find myself increasingly prone to resort to the ESP series for my boards. They have the speed, the RAM, the flash, the builtin USB for upload... You just slap it on and you have everything you'll ever need, for the price of $2. It is very easy to hand solder, and though it's kinda bulky, if you factor in all the components that you'd otherwise need for other microcontrollers, such as programming headers, crystals, capacitors, flash chip, USB to UART converters... It is actually very compact. Sure they rob you of the joy of bare metal programming with the joke of an operating system called RTOS, the Frankenstein toolchain called IDF piling together every popular build system out there, and it takes half a decade to upload some code to it, but if you're a normal person and not an AVR assembly masochist like myself, there seems to be not much reason otherwise to choose anything other than the ESP for something general-purpose.

One bonus perk from the ESP is the wireless connectivity that comes for free, which I never cared much for, but since it has it why not add some BLE (bluetooth low energy) to my machine, so now people can hack it from far away?

I designed the BLE commands to mirror the serial USB commands. Basically instead of writing the same command to serial, you write it to a so called "characteristic". Then the ESP, upon seeing that the characteristic has been tampered with, will execute that command, and overwrite the characteristic with the word "OK" when it's done, which tells you that you can overwrite that in turn with the next command. The system seemed to work quite reliably.

Web Interface

Now that I've been mostly satisfied with the basic hardware, firmware and software of the seal carver, it is time to make some fancy web interfaces. (Well, not that fancy actually, as I have long been disillusioned about web design, and resorted to making all my sites with the barest, plainest and most devoid of CSS ways.)

The idea with the web interface is that one without too much experience with Chinese seals can design a sufficiently good looking one right on the webpage, and have it be milled by my machine right away. Meanwhile, one who is an expert in Chinese seals should not find the tool too shabby, and should still be able to make the expert-level seals they want with the tool.

I divided the page into four panels: the first is an input canvas, the user can either draw something directly in it or upload an image; the second is for processing the input image; the third is for toolpath generation, and the fourth is four communication with the machine (i.e. actually sending the toolpaths).
The first panel allows to different types of inputs, either vector or raster. With raster inputs, one draws a "pixel art" for their seal (for some seal styles do have this grid-like system), and the post-processing algorithm is a upsampling pass followed by a threshold pass, basically creating these rounded shapes resembling the marks made on real seals. Different upsampling algorithms (nearest-neighbor, bilinear, bicubic, lanczos, etc.) create different looks.

For vector inputs, one can draw polylines -- also snapped to resizable grids. Then, the aforementioned differential growth can be optionally applied as post-processing pass in the second panel. The scale and iterations, among a bunch of other parameters, allow the user to drastically change the output.

For the third panel, the toolpath is generated. If the previous output is raster, it would be traced. The user can also invert the seal -- you can either carve away the pattern you want, or carve away everything other than the pattern you want. For the vector, non-inverted combination, the toolpath trivially follows the input polylines, for other combinations, the distance transform algorithm is used to clear out areas of material with concentric loops.

With the fourth and final panel, one can connect to the machiine with either a serial port, or bluetooth. One can manually control the X, Y, and Z axes by small or large steps, send a "clearing pass" for flattening the surface or removing a botched attempt, or send the toolpath generated from the previous panel.

I could give the interface a more ergonomic and aesthetically pleasing makeover, perhaps add some dataflow things to it, but I felt that it is in a good place right now (Carved some nice seals with it). I wanted to work on some other projects over the summer.

Conclusion

I'm quite happy with what I've done. It is the first time I've built a machine, and I feel like I have a better understanding of all the parts by building it from scratch myself. It might be a pretty basic CNC with a basic motion system and does basically what CNC's do, but it is mine and I built it and it is special to me. I took care to give it the type of aesthetic I like, and came up with creative solutions to solve various problems and add various features. At the beginning I was just some programmer/artist dude that have no understanding of how mechanical things work, but as I started doing things the problems started to emerge, and that's when I just used my general problem solving skills to tackle them. I guess that worked!