AutoLevellerAE (Advanced Edition)

The Java based controller for 'levelling' GCode file for use on a CNC machine

Probes the surface to be etched then uses this information to adjust the Z height using bilinear interpolation

  • Can be used on multiple operating systems
  • Helps reduce air cuts when etching
  • Can equally be used for etching other projects such as a metallic project box
  • Compatible with Mach3, LinuxCNC and TurboCNC
AutoLevellerAE Basic Tab AutoLevellerAE Mesh Tab
AutolevellerAE is operation (Basic Tab v.0.6.2) AutolevellerAE is operation (Mesh Tab v.0.6.2)
w/o AutoLeveller with AutoLeveller
Circuit milled without AutoLeveller Circuit milled with AutoLeveller

AutolevellerAE accepts a GCode file as input and outputs a modified GCode file. Please visit the AutoLeveller website for more.


The source code can be retrieved by typing at a git command prompt...

git clone


Make sure Java8 SDK is intalled AutolevellerAE is a Maven project so it can be easily imported into most IDE's The quickest and easiest way to compile and run all tests including integration tests is by typing at a Maven installed prompt...

mvn clean install verify

To run only unit test type...

mvn clean test

Brief overview of important classes and methods

Many of the classes in this project are important and you should investigate them, and any test code which uses them whether you want to create a new project based on this one or want to make changes to the existing code. Having said that, here are a few of the more "prominent" ones...


This class is constructed from a GCode String and the previous GCodeState, where the first State is passed an Optional.empty() GCodeState. Each String is parsed into usable objects such as Words which make up the String. The modal state of the code is stored and obtained via a call to getModalWords() or getModalWordByGroup(WordGroup wordGroup) if the WordGroup is known. You can chain together multiple lines of GCode by passing the previous GCodeState to the new one. For example...

public void stateEquals()
    GCodeState state1 = GCodeState.newInstance(Optional.empty(), "N10 G00 G1 G2 G3");
    GCodeState state2 = GCodeState.newInstance(Optional.of(state1), "#3 = 10");
    GCodeState state3 = GCodeState.newInstance(Optional.of(state2), "F3000");
    GCodeState state4 = GCodeState.newInstance(Optional.of(state3), "N50 S20000");

    assertThat(state4.getModalWords(), containsInAnyOrder(Word.createPair('G', BigDecimal.valueOf(3)),
            Word.createPair('F', BigDecimal.valueOf(3000)),
            Word.createPair('S', BigDecimal.valueOf(20000))));

    assertThat(state4.getPreviousState().get(), equalTo(state3));

GCodeState's store 1 and only 1 previous state which prevents a single state from becoming too large as more and more states are chained. For most operations we only ever need the most recent previous state whilst the modal state is known for an unlimited number of chained states. This makes GCodeState's extremely robust.


Each line of Gcode may need different processing and need to be rewritten after levelling for example. This is what a GCodeProcessor does. If you need to process the GCode differently, you only need to implement this interface to process states how you want.

public interface GCodeProcessor
    void interpret(List<GCodeState> states);

    List<GCodeState> processState(GCodeState gCodeState);

    default Optional<GCodeProcessor> nextProcessor(){return Optional.empty();}

    default void finish(){}

The GCodeInterpeter is an abstract class which implements GCodeProcessor and makes final the interpret() method. New Processor classes should extend GCodeInterpeter rather than implement GCodeProcessor directly. What interpret() does is processes all given states then recursively calls interpret() on any nested GCodeProcessor passing the newly processed states. In this way GCodeProcessor can be chained.

public abstract class GCodeInterpreter implements GCodeProcessor
    public final void interpret(List<GCodeState> states)
        List<GCodeState> returnedStates = states
            .flatMap(gCodeState -> processState(gCodeState).stream())

        nextProcessor().ifPresent(gCodeProcessor -> gCodeProcessor.interpret(returnedStates));

This chaining of processors is shown here...

public static void writeFullLevelledFile(ALModel model) throws IOException
    if (model.isReadyForWriting())

        GCodeReader reader = PlainGCodeReader.newInstance(new FileReader(model.getOGF().get().getFilename()));
        GCodeProcessor writer = WriterProcessor.newInstance(Optional.<GCodeProcessor>empty(), model.getOutputFilename().get(), true);
        GCodeProcessor leveller = LevelerProcessor.newInstance(Optional.of(writer), model.getMesh().get());
        GCodeProcessor segmentor = SegmentorProcessor.newInstance(Optional.of(leveller), model.getSegLen().doubleValue());
        GCodeProcessor replacer = ReplacerProcessor.newInstance(Optional.of(segmentor));


A Mesh is a grid of ThreeDPoints representing the area to be probed where each point is a probe point. The size of the grid and value of is points are determined by the members which are set during construction of an instance via a builder. These few required inputs on construction result in a mesh object with probing points pre-calculated and is one of the most useful classes in the project. The test code below shows how to construct a Mesh instance and obtain the value of any pre-calculated point...

public void builderInputs()
    Mesh mesh = new Mesh.Builder(Units.INCHES, Software.LINUXCNC, BigDecimal.ZERO, BigDecimal.ZERO,
            BigDecimal.valueOf(5), BigDecimal.valueOf(3))

    assertThat(mesh.getUnits(), equalTo(Units.INCHES));
    assertThat(mesh.getxValue(), equalTo(BigDecimal.ZERO.setScale(Mesh.SCALE)));
    assertThat(mesh.getyValue(), equalTo(BigDecimal.ZERO.setScale(Mesh.SCALE)));
    assertThat(mesh.getxLength(), equalTo(BigDecimal.valueOf(5).setScale(Mesh.SCALE)));
    assertThat(mesh.getyLength(), equalTo(BigDecimal.valueOf(3).setScale(Mesh.SCALE)));
    assertThat(mesh.getProbeDepth(), equalTo(BigDecimal.valueOf(-0.05).setScale(Mesh.SCALE)));
    assertThat(mesh.getPointSpacing(), equalTo(BigDecimal.valueOf(0.5).setScale(Mesh.SCALE)));
    assertThat(mesh.getzFeed(), equalTo(BigDecimal.valueOf(7.0).setScale(Mesh.SCALE)));
    assertThat(mesh.getXyFeed(), equalTo(BigDecimal.valueOf(30.0).setScale(Mesh.SCALE)));
    assertThat(mesh.getProbeClearance(), equalTo(BigDecimal.valueOf(0.8).setScale(Mesh.SCALE)));
    assertThat(mesh.getSafeHeight(), equalTo(BigDecimal.valueOf(3.0).setScale(Mesh.SCALE)));

    assertThat(mesh.getPoint(0, 0).get(), equalTo(ThreeDPoint.createPoint(BigDecimal.valueOf(
            0.00000).setScale(Mesh.SCALE), BigDecimal.valueOf(0.00000).setScale(Mesh.SCALE), "#500")));
    assertThat(mesh.getPoint(6, 10).get(), equalTo(ThreeDPoint.createPoint(BigDecimal.valueOf(
            5.00000).setScale(Mesh.SCALE), BigDecimal.valueOf(3.00000).setScale(Mesh.SCALE), "#576")));

All the above code is taken from the source project code but can and will change over time. Please see the current code for the latest versions


<div>Segment Length Dialog icons made by <a href="" title="Freepik">Freepik</a> from <a href="" title="Flaticon"></a>; is licensed by <a href="" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>