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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions