speakr.blog

Thomas Huijzer

Thomas Huijzer

| 1082 views

Image to G-code in Javascript (part 3)

Part 1: https://speakr.blog/thuijzer/posts/2 Part 2: https://speakr.blog/thuijzer/posts/3

Setting up G-code

Now we have the raw pixels that match our workpiece we can start to write G-code. First we need to think about our machine and it's coordinate system. In this example I use a 3-axis machine where the x direction is forwards, the y direction is to the right and the z direction is up. We also need to think about our starting position. I like to use the left bottom of our workpiece as zero for the x and y position and the surface of the workpiece as zero for the z position. Let's start to write some G-code.

The first G-code we write will tell the machine some basic information. We tell the machine we are going to program in mm units, we are using absolute programming (this means that all coordinates are absolute positions) and we tell the machine where it starts. Note that we are using `\n` after each line. This is a 'new line' character. Everything between parentheses are comments the machine does not read. ``` let gcode = ''; gcode += 'G21 (Programming in millimeters)\n'; gcode += 'G90 (Absolute programming)\n'; gcode += 'G92 X0 Y0 Z0 (Tell the machine it is at 0, 0, 0)\n'; ``` When you position your machine at the left bottom, right above the surface of your workpiece and run the above code it will know it is at position `X0 Y0 Z0`. This will be our starting position. We now need to turn the spindle on to start milling. But if we do that right now we might damage our workpiece. So first let's move the machine up a little before we turn the spindle on. It can be convenient to store this 'up position' as 'travel position'. Then we can use it every time we travel. Maybe a travel position of 5mm above our workpiece is a save distance. Let's use that. The `G0` command quickly moves the machine. Note that we are using back ticks for the Javascript strings so we can insert variables more easily with `${myVariable}`. ``` let travelZ = 5; let gcode = ''; ... gcode += \`G0 Z${travelZ} (Move the machine up)\n\`; ```

The spindle is now 5mm above the workpiece. Now we can safely turn the spindle on. This is done with the `M3` command and the `S` parameter. Different machines have different values for this, but a lot of machines use `1000` as maximum value. This means that if your spindle can run at 12000 rpm and you use an S-value of 1000 it will run at full speed. An S-value of 500 will run it at 6000 rpm and so on. But we run it at full speed in this example: ``` gcode += 'M3 S1000 (Turn the spindle on at max speed)\n'; ```

Drilling holes

Since our image is the same size as the amount of holes we want to drill we can just iterate over the pixels and calculate the `x` and `y` position in `mm` based on the `drillBitSize` we set earlier. For this we create a loop. In this loop we calculate the pixel index (because imageData is an R,G,B,A array) and we calculate the x and y position in mm. ``` for(let x = 0; x < canvas.width; x++) { for(let y = 0; y < canvas.height; y++) { let i = (x + y * canvas.width) \* 4; let xmm = x * drillBitSize; let ymm = y * drillBitSize; } } ``` We now know the x and y position in mm so we can move the spindle to that location. Because the spindle is still hovering above the workpiece we can use a quick `G0` travel: ``` ... let i = (x + y * canvas.width) \* 4; let xmm = x * drillBitSize; let ymm = y * drillBitSize; gcode += \`G0 X${xmm} Y${ymm}\n\`; ... ``` The spindle is now at the right location so we can lower it to start drilling.

But first we must know how deep to drill. We calculate this by taking the gray value of the pixel and then divide it by 255. This will give us a depth value in the range of 0 to 1. 0 for black, 1 for white. ``` let gray = ( imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2] ) / 3; let depth = 1 - gray / 255; ``` Notice that we subtracted the depth value from `1`. We do this because we want dark colors to have a higher value than light colors. Now we can use the depth as multiplier for the maximum depth we want to drill. If we want to drill a maximum depth of, let's say 10mm we can calculate the Z-position like this: ``` let zmm = -(10 * depth); ``` Notice that we make it negative. That's because the zero position of Z is above the workpiece.

Let's start drilling. You do not want to use `G0` for this but `G1` instead. `G1` accepts a feed rate. We need this because different materials need different feeds rates. If we would drop the spindle at full speed we might cause damage.In this example I will use a feedrate of 100mm/minute. ``` gcode += \`G1 Z${zmm} F100\n\`; ``` After drilling we lift it up again to the travel position. ``` gcode += \`G1 Z${travelZ} F100\n\`; ``` We now completed the loop. The spindle is now at the right Z-position and can travel to the next pixel.

Finishing the G-code

When the program is done we can finish the G-code by turning off the spindle and moving back to the home position. After the loop we can add this code: ``` gcode += 'M5 (Turn the spindle off)\n'; gcode += 'G0 X0 Y0 (Move home)\n'; gcode += 'M2 (DONE)\n'; ```

Now we got our complete G-code in the `gcode` variable. To use it we can put it in a HTML textarea making it possible to copy past it: ``` document.getElementById('myTextarea').value = gcode; ```

Optimizations

This blog post is giving a very simple example of a G-code path. Of course this is not the best path; every time the spindle is at the end of the workpiece it moves all the way back to X0 while it would be better to only move Y. But I will leave those optimizations up to you ;)

The complete code

At the end of this series I will also post the complete code. If you still have any questions, or when you noticed an error, please feel free to contact me at thomas@thuijzer.nl

``` let image = new Image(); image.addEventListener('load', function() { let workPieceWidth = 100; // mm let workPieceHeight = 200; // mm let drillBitSize = 2; // mm let horizontalPixels = Math.floor(workPieceWidth / drillBitSize); let verticalPixels = Math.floor(workPieceHeight / drillBitSize); let travelZ = 5; let feedRate = 100; let maxDepth = 10; let canvas = document.createElement('canvas'); canvas.width = horizontalPixels; canvas.height = verticalPixels; let imageP = image.width / image.height; let canvasP = canvas.width / canvas.height; let scale = 1; if(imageP < canvasP) { scale = image.width / canvas.width; } else { scale = image.height / canvas.height; } let srcX = (image.width - canvas.width \* scale) \* 0.5; let srcY = (image.height - canvas.height \* scale) \* 0.5; let context = canvas.getContext('2d'); context.drawImage( image, // source image srcX, // source x position srcY, // source y position canvas.width * scale, // source width canvas.height * scale, // source height 0, // destination x position 0, // destination y position canvas.width, // destination width canvas.height // destination height ); let imageData = context.getImageData( 0, // x position 0, // y position canvas.width, // width canvas.height // height ); let gcode = ''; gcode += 'G21 (Programming in millimeters)\n'; gcode += 'G90 (Absolute programming)\n'; gcode += 'G92 X0 Y0 Z0 (Tell the machine it is at 0, 0, 0)\n'; gcode += \`G0 Z${travelZ} (Move the machine up)\n\`; gcode += 'M3 S1000 (Turn the spindle on at max speed)\n'; for(let x = 0; x < canvas.width; x++) { for(let y = 0; y < canvas.height; y++) { let i = (x + y * canvas.width) \* 4; let xmm = x * drillBitSize; let ymm = y * drillBitSize; gcode += \`G0 X${xmm} Y${ymm}\n\`; let gray = ( imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2] ) / 3; let depth = 1 - gray / 255; let zmm = -(maxDepth * depth); gcode += \`G1 Z${zmm} F${feedRate}\n\`; gcode += \`G1 Z${travelZ} F${feedRate}\n\`; } } gcode += 'M5 (Turn the spindle off)\n'; gcode += 'G0 X0 Y0 (Move home)\n'; gcode += 'M2 (DONE)\n'; document.getElementById('myTextarea').value = gcode; }); image.src = 'https://www.example.com/myimage.jpg'; ```