I have a working CNC router and the controller to drive it. My next task is to transform my list of co-ordinates that make up my lizard into G-Code – commands that the controller will execute.
On the face of it, this seems a straightforward enough task. G-Code is just text, and is a list of simple “move to here, turn drill on, move to here, then to here” commands. But there’s more maths to deal with before we get to that point …
The problem with CNC routing
I have the co-ordinates that make the outline of my lizard. If this shape was to be laser cut, then the cutting width of the laser would be so insignificant (about 0.1mm) that I could just allow the laser to follow those co-ordinates exactly.
But the cutting width of a router bit is much more significant – about 3 or 4mm diameter. In this case, the cutter shouldn’t follow the path – it should follow a path outside of the shape, calculated to compensate for the cutting width. Otherwise, the router bit will cut away wood which should be part of the shape, making the shape too thin (putting 4mm channels between each shape).
So: I must write code which takes the shape of my lizard, and calculates a new path which will compensate for the drill width and produce the shape I require.
This is my completely arbitrary shape, invented to illustrate the problem. I have named him Herbert. He is made of eight points.
The first thing I do is to “explode” the shape – instead of considering it as a single shape, I consider it as a list of lines, where the end of one just happens to be the same as the beginning of the next.
Then I can move each line “out” by the drill radius. I do this by calculating each line as a vector, rotating it anti-clockwise by 90 degrees, and scaling it so that its magnitude is equal to the drill radius. That gives me the vector that should be applied to both ends of the line, in order to move it to its new position:
Do that to every line in Herbert, and we get this:
The dotted lines denote where the drill should go in order to cut a shape of the correct size. This leaves us with gaps on exterior corners, and crossovers on interior corners. So now we resolve those problems.
If we allowed the drill to travel the entire length of both dotted lines that make up an interior corner, the drill would cut into the material we want to keep:
In the above diagram, the drill would start at A (the circle denotes the size of the drill), then travel to B, before cutting another line from C to D. You can see that circles B and C would also take a portion of the shape with them – and if the drill were told to move from B to C without lifting, then it would cut away all the material on its way there, too.
Fortunately the fix is straightforward – we reduce both lines so that they only cut as far as they can. Conveniently, this is where lines AB and CD intersect! So we make line AB finish earlier, and CD start later:
So the drill will travel from A to B to C, and will not take any material from our shape.
There is one problem though: there’s a small amount of material that should have been cut away that hasn’t – marked in red in the above diagram. This is a side-effect of using a circular cutter to do the work – to beat this I must manually cut or file this away afterwards.
On exterior corners, the act of “exploding” the lines makes them move apart. Like this:
When the drill has travelled to circle A, it must then travel to circle B to begin the next edge. But it must not travel from A to B in a straight line, as this will cut through the tip of the shape.
Instead, we make it cut in a small arc – from the centre of A to the centre of B, using the apex of the corner as the arc’s centre. This will ensure that the centre of the drill is always the same distance from the apex, keeping that same path “outside” of the shape that we are striving for:
This will give us perfect exterior corners – nice and pointy, exactly what the original data describes. But … we have that problem of curves of wood left in interior corners, don’t we? Wouldn’t it be nice if we could round-off exterior corners in the same manner, so that they will fit into the interior rounded corners of adjacent shapes?
Note that we can only do this because this particular project knows that the shapes tessellate, and the same size drill is used for all cutting. So we can calculate how much to round the corner off. It isn’t strictly necessary – it’s just a bit of polish. But I need 200 lizards to cover my dining room, so I might as well get the shape perfect before I start – otherwise I’ll be spending quite a lot of time manually buffing corners into a curve before they are laid.
To “round off” the corner, we adjust the arc on that corner so it starts earlier and finishes later. To begin, we calculate where the drill bit would be if this were an interior corner. That gives us the centre of the arc. Then, we use simple geometry to calculate where to start and end the arc. The radius of this arc is double the radius of the drill bit.
Generating the G-Code
After all that geometry, hand-waving, and muttering to oneself whilst scribbling crappy diagrams … generating the actual G-Code is pretty trivial. We just generate the commands in a textfile, to be sent to the GRBL unit with any suitable comms or controller package.
A few commands at the top of the file tell GRBL that we’re working in millimetres, reset any co-ordinate offsets, and to turn the drill on.
G21 G80 G90 M3
Then we supply co-ordinates for each point the drill should travel to:
Z0.0000 X63.8746 Y104.2602 Z21.0000 X114.3274 Y112.6689 ... etc ...
Then we finish off by lifting the drill, turning it off, and returning to the “home” position.
Z0.0000 M5 X0.0000 Y0.0000