Escher Lizard Flooring Project (part 5)

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.

herbert01

The Algorithm

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:

'r' is the drill radius. The line that 'r' marks also happens to be the vector to apply to both ends of the line.

‘r’ is the drill radius. The line that ‘r’ marks also happens to be the vector to apply to both ends of the line.

Do that to every line in Herbert, and we get this:

herbert02

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.

Interior corners

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:

herbert03

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:

herbert04

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.

Exterior corners

On exterior corners, the act of “exploding” the lines makes them move apart. Like this:

herbert05

The arrows denote the direction of the drill’s travel. Circle A is where the first line will end, and circle B is where the second line will start.

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:

Greyed-out the other lines, to better show the arc.

The other lines are greyed-out to better show the arc.

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

Easy.

[Part 1] [Part 2] [Part 3] [Part 4] [Part 6] [Part 7] [Part 8]


2 thoughts on “Escher Lizard Flooring Project (part 5)

  1. You did all a really great job! Thank you so much for sharing, impressive descriptions too…
    I was just asking myself something about the problem of the interior corners that made you file or manually cut the red part in the B point. Another way to solve without the manual labor could have been to cut the same round shape, but positive, in all the exterior corners? Round corners usually are also less prone to be dented. Or maybe I am missing some geometrical aspect?

  2. Yes, that was the approach I took – see the text, just after the last diagram. I round off the exterior corners to the same amount as the internal corners. It is an obvious improvement on the tails, as they’re the sharpest exterior corner.