Adventures in CNC Milling and Updates

Adventures in CNC Milling and Updates01.02.2009 17:52:32

The last few months have been very busy here at Ruin & Wesen, mostly focusing on building solid electronic devices and casings. It definitely was a big undertaking moving from hand-building one-off casings to producing bigger batches of industrial-quality casings, and this also has been the reason for most of the delays and redesigns of our devices. Furthermore, the idea of opensource controllers evolved quite a lot from when Ruin & Wesen was started. At first, the idea was to market these Elektron controllers because I found them so incredibly useful when playing live. Today, these controllers are completely and freely programmable by everyone, and they can be used not only to control Elektron machines or other synthesizers, but can also work as sequencers, as a platform to sketch algorithmic music ideas, as a clever evolving controller, basically as anything you want.

The whole process is taking shape, and I will post videos, pictures and HOWTOs once the first devices have made it to the door. It took some incredibly hard work to get to this stage, and had I known this a few months ago I'm not sure I would have got into this whole thing. In the meantime, look at this low-res webcam shot of this beautiful PCB-assembly of the minicommand (which is pretty much the controller advertised on the website) :)

It has been a rough and long ride, but I am really glad I took the time to do this right. The devices are rock solid, nicely heavy, have beautiful aluminium casings (pics soon), and the cost didn't go through the roof. In this blog post I will show a bit of the software and hardware I implemented to make this all possible. I am also really happy that after setting up, we will finely be able to focus on Ruin's analog creations and present a whole different side of the company that hasn't been really featured yet. There are some very very exciting things in the making :)

To mill the casings, I got a small CNC mill. It can mill wood, copper, aluminium and other softer materials. The first step to using the mill was to get acquainted with CAD software to draw workpieces. The CNC control software by the manufacturer takes DXF, Postscript or G-Code files as input. The first tries where thus to draw DXF files of the casings and mill those. This is basically 2D milling, because the drawings are two-dimensional. I quickly realized however that this whole process was pretty tedious, as it required learning a CAD tool, redrawing the casing which was basically already present in the PCB layout tool we use, and then import the generated vector file into the milling software, fixing all the glitches that would happen in the process (inverted polylines, bad layers, etc...). People who have used a CNC-Laser or a CNC-Mill probably know about what kind of trouble I'm talking about.

Being someone who likes to write everything from scratch, I decided to write a tool to generate G-code that would allow me to write code like: mill a rectangle of 5x5mm with the tool outline the paths from the inside, then rasterize a cylinder with a 3mm diameter at this position, etc... G-code is basically just a collection of moves that the mill has to make: go to (3, 5, 6), then go to (4, 5, 6), then go to (6, 7, 7) in an arc, so it was pretty easy to generate the actual file. I decided to write the software in Common Lisp, as it is the language I "think" in, and one that would allow me to quickly prototype things.

The first step was to find a representation for the G-code that would get generated. The choice was pretty easily settled, as Common Lisp uses lists as one of its core data structures. Each G-code instruction is represented as a list, with the first element being the instruction, and the rest being the arguments. A few small functions to print these lists out, and the Gcode generator was finished. For example, the list representing a move to (1, 2, 3) with feedrate of 600 mm/minute looks like this:

(G-LINE (:G01 (:X 1) (:Y 2) (:Z 3) (:F 600)))
and is printed out as the G-code line:
G01 X1 Y2 Z3 F600.0

The big step now was to generate useful movements. G-code can move in absolute coordinates and in relative coordinates. A first try to implement both ended in big code and was quickly refactored into using only relative coordinates. However, functions implementing relative moves were kept, as they are pretty useful to mill geometrical shapes. Using only absolute coordinates allows for the use of transformation matrices to scale, rotate and translate milling instructions. This allows me for example to panelize parts easily (mill a number of identical parts out of one material part, or mill a number of casings at the same time). Handling movements and absolute coordinates is done by a number of functions implementing G-code primitives: GOTO (move with maximal speed), MILL (move with milling speed), ARC-CW (mill in a clockwise arc) and ARC-CCW (mill in a counter-clockwise arc). No other G-code movement primitives are used. These functions keep track of start and endpoint (which is useful to calculate relative movements, to calculate the time a program will take to execute, and to calculate the size of a milling part). All these movement primitives come in an absolute version (for example GOTO-ABS, which goes to the absolute (x, y, z) coordinates, and GOTO-REL, which goes to the relative (x, y, z) coordinates from the current point). However, the G-code that finally gets generated is all absolute, and the handling of relative coordinates is done internally in my milling software.

At first, these instructions all just generated G-code lists, but it quickly became pretty evident that it would be much more useful to collect these movements in a more intelligent way. Thus, I implemented two data structures which are called PROGRAM and PASSES. A PROGRAM contains everything that pertains to a milling process, for example milling a whole midicommand case. It contains the engraving, the case-milling, maybe even the milling of a PCB-prototype. All the different parts of the process, which can use different tools and even different working materials, are kept together in individual PASSes. A PASS has a name and a list of G-code instructions. When writing out a PROGRAM to a G-code file, PASSES can be ordered in a certain way, and each PASS is also written out as an individual file. One can panelize individual passes, or panelize the whole program for example. Here is what generating the G-code for a wooden cube looks like (the passes are "mill", "drills", "circles", "rounding" and "bridge-cut", and the main file first mills the circles present on the inside of each cube face, then the rounding steps, then the outline, and finally cuts through the bridges holding the workpiece in place):

GCODE> (program-to-file (small-cube-schedule) "/Users/manuel/nc/cube.nc"
        :order '("circles" "rounding" "mill" "bridge-cut"))

saved (outline mill rounding drills circles) to /Users/manuel/nc/cube.nc
saved bridge-cut to /Users/manuel/nc/cube-bridge-cut.nc
saved rounding to /Users/manuel/nc/cube-rounding.nc
saved circles to /Users/manuel/nc/cube-circles.nc
saved drills to /Users/manuel/nc/cube-drills.nc
saved mill to /Users/manuel/nc/cube-mill.nc

The reason for passes came when I was building the first test application for my milling software, which was milling wooden boxes using a one-pass boxtail design. I had to mill the outline of the individuals sides of the box, but also mill a small part out of the interior of the side, closely following the outline, so that they would fit together (as you can't do sharp straight interior edges with a CNC mill because of the diameter of the milling tool). Have a look at the following picture to see what I'm talking about (this is a screenshot out of the CNC-control software that came with the mill): the small lines above the outline take a small amount of material out so that it fits the corners of the boxtail of the opposing side.

I call the step of removing the small amount of material "rounding", because in a first try it was actually code to round the edge of the wood. It took way too much time however, so I decided to mill the straight line, which would get filled with wood glue anyway. Here is a few pictures of what the actual milled wood pieces look like (I use the cheapest poplar plywood I can find to prototype things). The cubes are later sanded down, painted, and fitted with LED lamps and reflective paper to make nice and cute pulsing LED lamps. I also use this code to mill the casings for the Ruin & Wesen devices (more about that in later postings).

Step 1:

Step 2:

Step 3:

Step 4:

I ended up writing the whole outlining code twice to generate the rounding steps at the correct coordinates, and thought that it would be very nice to generate steps in a different "pass" at certain points of generating the outline. Now I can write things like this, which would place a drill at a certain point I reached while milling the outline. This allows me to place drills at strategic points, but then have them all stored in a separate file with a different tool. I use this for example to generate temporary drills to hold a workpiece on the working plate, or to skip steps in the milling of an outline and store them in a later pass to have bridges that hold the milled piece in place until all the steps are done (you can see the temporary drills in the pictures above).

(with-pass ("outline")
   (mill-relative :x 10 :y 10)
    (let ((outline-x (current-x))
          (outline-y (current-y)))
       (with-pass ("drill")
          (drill :x outline-x :y outline-y :depth 0.4))))

I will continue this walkthrough of my CNC software in following posts this week, as it is a long and intense story :) To list just a few features I implemented: generating milling code out of processing.org sketches, milling wooden prototypes, casings and pcbs directly out of the EAGLE pcb layout program, tracing bitmaps, offsetting and tracing curves, rasterizing 3D surfaces, panelizing passes, optimizing drills with genetic traveling salesman, and much more.