« Back to home

Massage Your Gcode With Python

Since I first acquired my CNC machine I started building a set of tools in Python to let me generate and manipulate gcode files. I have now released this code on GitHub under a Creative Commons Attribution-ShareAlike license in the hope it will be useful to others as well.

Disclaimer: Please be careful before using any of the gcode generated by these tools on your actual machine. Although I use it successfully myself on a range of projects I cannot guarantee that it won't damage your machine or behave differently in your environment. A good check is to run the resulting code through OpenSCAM before using it on an actuall CNC machine.

About The Code

So, what's in the box? The code consists of a simple class framework to load, manipulate and save .ngc files as well as a handful of simple (and complex) command line utilities. The code base is definitely a work in progress, so please don't expect a perfect 'out of box experience' - it has built up over the past 9 months and undergone several refactorings and rewrites as I added the tools and functionality I needed for a particular use case.

PCB Layout Image

There is no 'pip install' option, you will just have to clone the repository and run the tools directory. You will need Python 2.6 or 2.7 to run the tools and there are a few dependencies you will need to install as well:

  1. pillow - the Python Imaging Library. This is used to generate PNG representations of gcode files.
  2. matplotlib - this is only used in the probeinfo.py utility to generate height map images from bCNC probe files.

All the tools use millimeters for units (and gcode files in inches will be converted to millimeters when they are loaded). In most cases the tools assume you are doing simple 2D operations with single cut (Z < 0) and safe (Z > 0) depths - gcode that uses a range of Z levels will probably not fare well.

To avoid having to specify parameters on every tool the library looks in the file 'gcode.json' (included in the repository) for a set of default values to use if the associated command line option is missing. That file looks like this ...

# This file contains default values for all tools as well as the standard
# prefix and suffix to apply to generated files.
  "prefix": [
    "G21 (Use mm)",
    "G90 (Set Absolute Coordinates)",
    "G17 (XY plane selection)",
    "G00 Z${safe} (Move to safe height)",
    "G00 Y0 X0 (Go home)",
    "(End of prefix)"
  "suffix": [
    "G00 X0 Y0 (Go home)",
    "M02 (Program End)",
  "defaults": {
    # Safe height for rapid movements
    "safe": 3.0,
    # Cutting depth
    "cut": -1.0,
    # Cutting depth (for PCBs)
    "pcbcut": -2.0,
    # Feed rate for cutting operations
    "feed": 254

I've done my best to make the utilities self documenting, simply run them without any arguments to get a list of options available. The best way to see how it works though is to look at the source code itself.

The remainder of this post gives an overview of the classes provided and how to use them, if you are only interested in the existing set of utility programs you can skip ahead to the end of the post.

GCode and GCommand Classes

All of the supporting code lives in the util package in the repository. The two main classes of interest are GCommand which represents a single gcode command and GCode which represents a sequence of commands (basically the whole .ngc file).

Python 2.7.8 (default, Jun 18 2015, 18:54:19)  
[GCC 4.9.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.  
>>> from util import *

You can either create a GCode instance by loading a .ngc file or by building it up line by line in your program. The GCode instance will update the bounding box that contains the cutting operations automatically.

>>> gcode = loadGCode("scratch/pcbtest_02_bottom_06.ngc")
>>> print gcode.minx, gcode.maxx
0.0 115.3068  
>>> print gcode
X: 0.0000, 115.3068 Y: 0.0000, 51.7332 Z: -0.0850, 3.0000  

Once the GCode instance is created you can inspect the individual commands by looking at the lines attribute.

>>> print len(gcode.lines)
>>> print gcode.lines[0]
G21 (Use mm)  
>>> print gcode.lines[10]
G01 X33.9532 Y5.8100  

Each entry in the lines list is a GCommand instance. This splits the gcode into it's command and parameters so you can easily manipulate it.

>>> cmd = gcode.lines[10].clone()
>>> dir(cmd)
['F', 'I', 'J', 'K', 'P', 'R', 'X', 'Y', 'Z', '__doc__', '__init__', '__module__', '__str__', 'clone', 'command', 'comment', 'matches']
>>> print cmd.X
>>> print cmd.command

Each of the standard parameters for a gcode instruction is added as an attribute on the GCommand instance. If the parameter has not been assigned a value the attribute will be set to None. The command itself (eg 'G00' or 'G38.2') is stored in the command attribute.

You can create a GCommand directly either by passing the command you want to use as a string or by setting the attributes after construction.

>>> cmd = GCommand("G0 X0 Y0")
>>> print cmd
G00 X0.0000 Y0.0000  
>>> print cmd.Z
>>> cmd = GCommand()
>>> cmd.command = "G00"
>>> cmd.X = 0.0
>>> print cmd
G00 X0.0000  

Finally you can save the modified (or created) gcode to a file with the saveGCode() function.

>>> saveGCode("scratch/blogpost.ngc", gcode)
>>> gcode.render("scratch/blogpost.png")

As a bonus you can also render the gcode to an image - this gives you a visual representation of what the result will look like.


The classes above provide enough functionality to programatically create gcode either from mathematical functions or by converting other formats such as graphics files into something that can be sent to a CNC machine. The next step is to be able to modify existing files and apply transformations.

To do this the framework provides a Filter class that can be applied to a GCode instance. Each filter implements a method called apply() which will be called with each command in turn. The method can return None to indicate the line should be ignored or a sequence of GCommand instances that will replace the original.

Rotation Filter

I have already implemented a number of filters in filters.py that provide support for translation, rotation, flipping and axis swapping. One useful filter will fix arcs for you by recalculating the center point - the LineGrinder tool in particular has a bad habit of generating arc commands with poor resolution. The implementation of this filter is an adaption of the LineBender code.

>>> gcode2 = gcode.clone(Rotate(90), Translate(dx = gcode.maxy))
>>> print gcode2
X: 0.0000, 51.7332 Y: 0.0000, 115.3068 Z: -0.0850, 3.0000  

Filters are applied with the clone() method on the GCode instance - this will return a new instance consisting of the modified commands. As you can see in the example above you can apply multiple filters in a single call - they will be applied in the order given.


I have also included a set of simple utilities in the repository as well:

  • areacut.py - generate a .ngc file to cut out a rectangular area at a specified depth.
  • ngcmerge.py - append multiple .ngc files together to create a single file.
  • reorigin.py - translate the file so the minimum X and Y cutting co-ordinates are at the origin.
  • bounds.py - display the bounding box for the cutting operations in the file and optionally generate an image of the tool path.
  • multipass.py - convert a single cutting operation into multiple passes at smaller cutting depths.
  • probeinfo.py - this one doesn't actually do anything with .ngc files, it creates a height map image from a bCNC .probe file.
  • rotate.py - rotate the file by a specified angle.
  • zlevel.py - change the cutting depth and safe level for the file.

There are also a few more complex utilities in there as well. The pcbpack.py script will lay out PCB isolation routing gcode files to fit a blank PCB panel and generated combined milling, edge milling and drilling files in the process. This tool will get it's own post in the next few days.

There is also svg2ngc.py which is an incomplete implementation of a SVG to gcode converter as well as a tool to generate SVG templates for cutting out double U style boxes.

What's Next?

Now that I've pushed the code to a public repository there is a bit of clean up work I would like to do - remove some redundant files and clean up the documentation for the project. While the PCB layout tool is mostly complete (enough that I can use it on a regular basis) it still needs some tweaking and the other larger tools are not yet finished.

As I said at the start of the post, it's still a work in progress but functional enough to be useful and the framework makes it very easy to implement new tools as they are needed. I will keep adding to it over time and, if you come up with useful additions please send a pull request to have them included.