Skip to content

Add a "how to create a new G-code" page to the docs. #3840

@andypugh

Description

@andypugh

After asking Github copilot a few questions about the parser, I asked it to document creating a new G-code.
From a quick read, I think this is largely correct.


Perfect! Now I have all the information needed. Let me create a comprehensive documentation page.


How to Add a New G-Code to LinuxCNC

This guide explains how to implement a new G-code command in the LinuxCNC interpreter. It covers the necessary source files, the modal group system, and walks through a concrete example.

Overview

Adding a new G-code to LinuxCNC requires modifications across several source files:

  1. Header Files - Define constants and function declarations
  2. Array/Enumeration Files - Register the G-code in modal groups
  3. Parser Files - Add code to parse the G-code parameters
  4. Conversion/Execution Files - Implement the actual G-code logic
  5. Documentation - Document the new G-code for users

Step 1: Define G-Code Constants

File: src/emc/rs274ngc/interp_internal.hh

G-codes are defined as symbolic constants in this header file. The naming convention is G_ followed by the G-code number, where decimal points are represented with underscores.

Examples:

enum GCodes {
    G_0 = 0,       // Rapid traverse
    G_1 = 10,      // Linear feed
    G_2 = 20,      // Clockwise arc
    G_3 = 30,      // Counterclockwise arc
    G_4 = 40,      // Dwell
    G_28 = 280,    // Return to home position
    G_38_2 = 382,  // Probe (notice: 38.2 becomes 382)
    // ... existing codes ...
};

Key Points:

  • The value is the G-code number multiplied by 10 (to handle decimal places)
  • G38.2 is represented as G_38_2 = 382
  • G59.3 would be G_59_3 = 593

File: src/emc/rs274ngc/rs274ngc_interp.hh

Declare any new conversion functions:

int convert_my_gcode(int g_code, block_pointer block, setup_pointer settings);

Step 2: Register in Modal Group System

File: src/emc/rs274ngc/interp_array.cc

G-codes are organized into modal groups. A modal group is a set of G-codes where only one can be active at a time. For example, G0, G1, G2, G3 are all in the same modal group (group 1 - motion modes).

Register your G-code in the gees[] array:

// From interp_array.cc
const int Interp::gees[] = {
/*   0 */   1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  // G0-G9
/*  10 */   1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  // G10-G19
/*  20 */   1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  // G20-G29
/*  30 */   1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  // G30-G39
/*  40 */   1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  // G40-G49
// ...
};

The index is the G-code value (remember, multiplied by 10). The value is the modal group number:

  • -1 means undefined/illegal
  • 0 = Non-modal group
  • 1 = Motion group
  • 2 = Plane selection (G17, G18, G19)
  • etc.

Modal Groups:

Group  0: Non-modal (G4, G10, G28, G30, G52, G53, G92, G92.1, G92.2, G92.3)
Group  1: Motion (G0, G1, G2, G3, G33, G33.1, G38.2-G38.5, canned cycles)
Group  2: Plane (G17, G18, G19)
Group  3: Distance mode (G90, G91)
Group  4: Arc distance mode (G90.1, G91.1)
Group  5: Length units (G20, G21)
Group  6: Cutter compensation (G40, G41, G42)
Group  7: Tool length offset (G43, G49)
Group  8: Coordinate system (G54-G59.3)
Group  9: Feed mode (G93, G94, G95)
Group 10: Retract mode (G98, G99)
...

Step 3: Add Parser Function (if needed)

File: src/emc/rs274ngc/interp_read.cc

If your G-code has parameters (X, Y, Z, I, J, etc.), you may need to add parsing logic. Most parameters are already parsed, but if you need custom parsing:

int Interp::read_my_word(char *line,      // G-code line being parsed
                         int *counter,     // Position in line
                         block_pointer block,   // Block being filled
                         double *parameters)    // Parameter array
{
    CHKS((line[*counter] != 'm'), NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED);
    *counter = (*counter + 1);
    
    // Parse the value (e.g., numeric value)
    double value;
    CHP(read_real_value(line, counter, &value, parameters));
    
    // Validate
    CHKS((value < 0.0), NCE_NEGATIVE_VALUE);
    
    // Store in block
    block->my_value = value;
    block->my_flag = true;
    
    return INTERP_OK;
}

File: src/emc/rs274ngc/interp_read_common.cc

Register the new word parser in read_items() function if it's a new word letter:

int Interp::read_items(block_pointer block, const char *blocktext, double *parameters)
{
    // ... existing code ...
    
    else if (blocktext[counter] == 'm')  // Your word letter
        CHP(read_my_word(blocktext, &counter, block, parameters));
}

Step 4: Implement Conversion Function

File: src/emc/rs274ngc/interp_convert.cc

This is where the actual G-code logic is implemented:

int Interp::convert_my_gcode(int g_code, 
                             block_pointer block, 
                             setup_pointer settings)
{
    // Validate input
    CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF),
         _("Cannot use G-code with cutter radius compensation on"));
    
    // Extract parameters from block
    double x = block->x_number;
    double y = block->y_number;
    
    // Perform calculations or state updates
    // ...
    
    // Update machine state
    settings->current_x = new_x;
    settings->current_y = new_y;
    
    // Queue canonical motion command (if motion-related)
    STRAIGHT_FEED(block->line_number, new_x, new_y, new_z,
                  AA_end, BB_end, CC_end, u_end, v_end, w_end);
    
    // Update motion mode
    settings->motion_mode = g_code;
    
    return INTERP_OK;
}

File: src/emc/rs274ngc/interp_convert.cc

Register the conversion function in convert_g():

int Interp::convert_g(block_pointer block, setup_pointer settings)
{
    int status;
    
    // ... existing code ...
    
    // For non-modal G-codes in group 0:
    if ((block->g_modes[GM_MODAL_0] == G_MY_CODE) && ONCE(STEP_MODAL_0)) {
        status = convert_my_gcode(block->g_modes[GM_MODAL_0], block, settings);
        CHP(status);
    }
    
    // For motion codes (group 1):
    if ((block->motion_to_be != -1) && ONCE(STEP_MOTION)) {
        status = convert_motion(block->motion_to_be, block, settings);
        CHP(status);
    }
    
    return INTERP_OK;
}

Step 5: Integrate with Motion Conversion

File: src/emc/rs274ngc/interp_convert.cc

If your G-code is a motion command (G0, G1, G2, G3, etc.), integrate it into convert_motion():

int Interp::convert_motion(int motion, block_pointer block, setup_pointer settings)
{
    // ... existing checks ...
    
    if ((motion == G_0) || (motion == G_1) || (motion == G_MY_MOTION)) {
        CHP(convert_straight(motion, block, settings));
    }
    else if (motion == G_MY_ARC_CODE) {
        CHP(convert_arc(motion, block, settings));
    }
    else if (is_a_cycle(motion)) {
        CHP(convert_cycle(motion, block, settings));
    }
    
    return INTERP_OK;
}

Step 6: Add Canonical Function (if needed)

File: src/emc/task/emccanon.cc or src/emc/rs274ngc/gcodemodule.cc

If your G-code requires a canonical function call to the motion layer:

void MY_GCODE_COMMAND(int line_number, double x, double y, double z)
{
    // Create message structure
    auto msg = std::make_unique<EMC_TRAJ_MY_GCODE_COMMAND>();
    msg->line_number = line_number;
    msg->x = x;
    msg->y = y;
    msg->z = z;
    
    // Send to motion queue
    emcmotQueue->write(*msg);
}

Step 7: Update Documentation

File: docs/src/gcode/g-code.adoc

Document the new G-code for users:

[[sec:G-MyCode]]
=== G-MyCode

G-MyCode is a new command that does something useful.

Syntax::
    G-MyCode X- Y- Z- <P->

Parameters::
    X- : X position
    Y- : Y position
    Z- : Z position
    P- : (optional) Some parameter

Example::
    G-MyCode X10 Y20 Z5
    G-MyCode X10 Y20 P1.5

Concrete Example: Adding G43.3 (Dynamic Tool Offset)

Here's a complete example of adding a hypothetical G43.3 (dynamic/temporary tool offset):

Step 1: Header (interp_internal.hh)

enum GCodes {
    // ... existing codes ...
    G_43_3 = 433,    // Dynamic tool offset
};

Step 2: Modal Group (interp_array.cc)

// In the gees array, position 433:
/*  430 */   7, 7, 7, -1, -1, -1, -1, -1, -1, -1,
//            ^G43.0 (=430) G43.1 (=431) G43.2 (=432) G43.3 (=433)

Modal group 7 is for tool length offset.

Step 3: Declaration (rs274ngc_interp.hh)

int convert_tool_length_offset(int g_code, block_pointer block, setup_pointer settings);

Step 4: Implementation (interp_convert.cc)

int Interp::convert_tool_length_offset(int g_code, block_pointer block, setup_pointer settings)
{
    EmcPose tool_offset;
    ZERO_EMC_POSE(tool_offset);
    
    if (g_code == G_43_3) {
        // G43.3: Apply dynamic offsets from block parameters
        if (block->x_flag) tool_offset.tran.x = block->x_number;
        if (block->y_flag) tool_offset.tran.y = block->y_number;
        if (block->z_flag) tool_offset.tran.z = block->z_number;
        // ... handle other axes ...
    }
    
    // Apply the offset
    USE_TOOL_LENGTH_OFFSET(tool_offset);
    
    // Update current position
    settings->current_x += (settings->tool_offset.tran.x - tool_offset.tran.x);
    settings->current_y += (settings->tool_offset.tran.y - tool_offset.tran.y);
    settings->current_z += (settings->tool_offset.tran.z - tool_offset.tran.z);
    
    settings->tool_offset = tool_offset;
    return INTERP_OK;
}

Step 5: Registration (interp_convert.cc)

In convert_g():

if ((block->g_modes[GM_TOOL_LENGTH_OFFSET] != -1) && ONCE(STEP_TOOL_LENGTH_OFFSET)) {
    status = convert_tool_length_offset(
        block->g_modes[GM_TOOL_LENGTH_OFFSET], 
        block, 
        settings);
    CHP(status);
}

Step 6: Documentation (g-code.adoc)

[[sec:G43.3]]
=== G43.3

G43.3 applies dynamic (temporary) tool offsets without changing the tool table.

Syntax::
    G43.3 X- Y- Z- A- B- C-

Parameters::
    X-, Y-, Z- : Linear axis offsets
    A-, B-, C- : Rotary axis offsets

Example::
    G43.3 X0.1 Y-0.05 Z0.2

Testing Your G-Code

After implementation, test your new G-code:

  1. Unit Tests - Add tests in unit_tests/interp/
  2. MDI Testing - Test via the GUI MDI interface
  3. File Testing - Test in G-code files
  4. Edge Cases - Test with cutter comp, unit changes, etc.

Key Patterns and Macros

Error Checking Macros

CHKS(condition, error_code, format_args)  // Check and return error
CHP(status)                                 // Check previous result and propagate error

Block Structure Fields

block->x_flag, block->x_number    // X axis flag and value
block->y_flag, block->y_number    // Y axis flag and value
block->f_flag, block->f_number    // Feed rate flag and value
block->g_modes[group_index]       // G-code modal values

Settings Structure Fields

settings->current_x, current_y, current_z  // Current position
settings->feed_rate                         // Current feed rate
settings->plane                              // Active plane (XY, XZ, YZ)
settings->distance_mode                      // Absolute or incremental
settings->cutter_comp_side                   // Cutter comp on/off/left/right
settings->motion_mode                        // Current motion mode

Important Considerations

  1. Modal Groups - Only one G-code per modal group can be active at once
  2. Coordinate Systems - Account for active coordinate system offsets
  3. Unit Conversions - Handle both metric and imperial units
  4. Cutter Compensation - Verify compatibility or add checks
  5. State Management - Update settings appropriately
  6. Canon Functions - Queue canonical functions for motion layer
  7. Parameter Tracking - Store state in settings and block structures

References

  • Enumeration Definitions: src/emc/rs274ngc/interp_internal.hh
  • Modal Groups: src/emc/rs274ngc/interp_array.cc
  • Parser Functions: src/emc/rs274ngc/interp_read.cc
  • Conversion Functions: src/emc/rs274ngc/interp_convert.cc
  • Example G-Codes: G2/G3 (arcs), G28/G30 (return to ref), G81-G89 (canned cycles)

This comprehensive guide should help you add new G-codes to LinuxCNC!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions