User Guide

Core Concepts

G-code Flavors

Different machines use different G-code dialects. G-code Reader uses flavor definitions (in gcode_reader.syntax) to map machine-specific command names and parameters to a normalized internal representation. You can select a flavor explicitly or let a machine class handle it automatically.

The Emulation Pipeline

Parsing a G-code file and emulating the process it describes follows a pipeline that goes through the following stages:

  1. Pre-processing (gcode_reader.preprocess) — resolves variables, calculations, and macros in the raw G-code text.

  2. Reading (gcode_reader.read) — tokenizes pre-processed lines into raw command strings.

  3. Parsing (GcodeParser) — converts raw strings into typed Command objects (moves, config changes, dwells, etc.) which describe the instruction sent to the machine.

  4. Machine emulation (Machine) — emulates commands against a machine model, tracking state (position, feed rate, active tool) and applying motion profiles.

  5. Output — produces an Operation (or specialized subclass) containing commands and resulting process data.

Command Objects

Every G-code instruction maps to a Command subclass:

Class

Description

Move

Linear movement to a location at a feed rate

Arc

Arc movement to a location about a center point

Extrude

Specifies a deposit volume or extrusion rate for a toolhead

MoveExtrude

Linear movement combined with extrusion (typical FFF/LFAM deposition move)

ArcExtrude

Arc movement combined with extrusion

Mill

Specifies a spindle rate for a milling toolhead

MoveMill

Linear movement combined with a milling spindle rate

ArcMill

Arc movement combined with a milling spindle rate

Dwell

Pause at the current location for a specified duration (G4)

Purge

Dwell while extruding (stationary deposition)

FeedRate

Feed-rate-only configuration update

Config

Generic machine configuration command (G/M-code with no dedicated subclass)

G

A G-code configuration command

M

An M-code configuration command

AbsolutePosition

Switch to absolute positioning mode (G90)

IncrementalPosition

Switch to incremental positioning mode (G91)

WorkCoordinates

Set work coordinate system origin (G54)

MachineCoordinates

Move in machine coordinate space (G53)

Comment

A comment-only line with no machine instruction

Operations

An Operation is the central output of the emulation pipeline. It pairs every input Command with a corresponding ProcessData record. It contains data for what the machine was told to do alongside what the machine model predicted at that step. Together, these two parallel lists form a complete record of the manufacturing process: toolpath geometry, timing, feed rate, tool orientation, and (for additive processes) deposited volume.

The relationship is one-to-one: commands[i] and process_data[i] always describe the same step. ProcessData is only populated after a Machine has processed a Operation; before that, process_data is empty and methods like get_bounds() and get_statistics() will warn and return None.

Specialized subclasses extend the base process data for different process types:

Class

Process data type

Additional fields

Operation

ProcessData

location, feed rate, elapsed time, distance

AdditiveOperation

AdditiveProcessData

deposited volume, bead area, extruder type

SubtractiveOperation

SubtractiveProcessData

spindle speed, removed volume

AdditivePart and Beads

AdditivePart is a geometric query layer on top of the process data from an AdditiveOperation. Its core is a summary DataFrame where each row represents one continuous move segment (deposition or travel), with start/end indices that index back into operation.process_data. A layer_normal vector partitions those segments into discrete layers.

This structure lets you ask spatial and temporal questions without scanning the full process data list:

# Iterate over continuous deposition paths as coordinate lists
for path in part.generate_deposition_paths():
    print(path)  # [(x, y, z), ...]

# Scalar properties derived from the bead cross-section model
print(part.n_layers, part.deposition_length, part.volume, part.mass)

A Bead defines a constant cross-sectional geometry of the extruded material (width, height, shape, density) and is used to compute volume and mass from path length.

Motion Profiles

The MotionProfile planner converts a sequence of programmed moves into a timed trajectory using a two-pass velocity planning algorithm modeled after GRBL-style firmware:

  1. Junction velocities — For each waypoint, a maximum cornering speed is derived from the junction-deviation model. The tool is treated as if it followed a small arc through the corner; the radius of that arc is set so the centripetal acceleration equals max_acceleration. A larger junction_deviation value lets the machine carry more speed through corners; a smaller value forces tighter slowdowns.

  2. Forward pass — Starting from rest, each segment’s exit velocity is capped by what is physically reachable from its entry speed over its length: v_exit sqrt(v_entry² + 2·a·d).

  3. Backward pass — Walking backwards from the terminal stop, entry velocities are tightened so every segment can decelerate to its required exit speed.

The result is that every segment receives consistent, physically achievable entry and exit velocities, and the segment’s own profile (Trapezoid or SCurve) handles the intra-segment kinematics from there.

If max_acceleration is not set, the corner model is skipped and the machine is assumed to change speed instantaneously — equivalent to point-to-point motion with no cornering penalty.

Segment types:

Class

Description

ConstantVelocity

Moves at a fixed speed with no ramp

LinearRamp

Linear acceleration from initial to final velocity

Trapezoid

Accelerate, cruise, decelerate — the default for most machines

SCurve

Jerk-limited S-curve profile for smoother transitions

The Trapezoid profile operates in two modes: distance-limited (ramps occupy fixed fractions of the move) or acceleration-limited (enforces a max_acceleration cap, collapsing to a triangular profile on short moves).

Configuring the planner:

from gcode_reader.emulate.motion import MotionProfile, SCurve

planner = MotionProfile(
    max_accleration=500.0,   # mm/s² — required for corner limiting
    max_velocity=100.0,       # mm/s — ceiling on all moves
    junction_deviation=0.05,  # mm  — tighter = slower corners
)

# Switch from Trapezoid (default) to SCurve
planner.profile_type = SCurve

machine.motion_profile = planner

Machine subclasses set max_acceleration from their firmware config when available. When reading an unknown machine or a file without firmware metadata, max_acceleration defaults to None and cornering is disabled.

Extending the planner for custom machines:

Machine-specific rules that go beyond the standard corner model — forced stops before layer changes, axis-specific speed caps, mandatory dwells — can be injected without touching the planner’s core logic by overriding _apply_machine_constraints in a MotionProfile subclass. The method receives the target-velocity and junction-velocity arrays immediately before the forward/backward passes run, so any constraints you impose are automatically propagated through the full toolpath by the backward pass.

from gcode_reader.emulate.motion import MotionProfile
import numpy as np

class MyMachineProfile(MotionProfile):

    def _apply_machine_constraints(
        self, initial_positions, final_positions, target_velocities, junction_velocities
    ):
        # Example: force a full stop before every pure-Z move (layer change).
        dxyz = final_positions - initial_positions
        is_vertical = (np.abs(dxyz[:, 0]) < 1e-9) & (np.abs(dxyz[:, 1]) < 1e-9)

        junction_velocities = junction_velocities.copy()
        for idx in np.where(is_vertical)[0]:
            if idx > 0:
                junction_velocities[idx - 1] = 0.0  # decelerate into vertical move
            junction_velocities[idx] = 0.0           # stop at end of vertical move

        return target_velocities, junction_velocities

BAAMMotionProfile uses this hook to enforce the Cincinnati BAAM’s layer-change stop requirement and per-axis velocity caps, and is a working reference implementation.

Working with DataFrames

The DataFrame interface is the fastest path for data analysis. It returns a flat Pandas DataFrame where each row is a waypoint in the toolpath:

import gcode_reader as gr

df = gr.gcode_file_to_dataframe("my_part.gcode")
# Typical columns: x, y, z, feedrate, time, extrusion, ...
print(df.dtypes)

For additive-specific analysis (event series, layer data), use:

event_series = gr.gcode_dataframe_to_event_series(df)

Or export directly from a G-code file:

gr.export_event_series_from_gcode("my_part.gcode", output_path="events.csv")

Exporters

Two exporters are available:

GcodeExporter converts parsed Command objects back to G-code strings or into a flat dict/DataFrame row. It uses the same syntax flavor system as the parser, so round-tripping between machines is straightforward. Custom exporters can be passed to any Machine at construction time.

VTKExporter is the most information-dense export option for additive parts. It converts an AdditivePart into a PyVista line-segment mesh where each cell is one deposition segment. All ProcessData scalars (feed rate, elapsed time, deposited volume, etc.) plus bead geometry and layer time are embedded as cell data, making the output directly usable in ParaView or any VTK-based pipeline for spatial analysis.

from gcode_reader.emulate.exporters import VTKExporter

# Export to a .vtk file
VTKExporter.additive_part_to_vtk(part, "output.vtk")

# Or get a PyVista PolyData object directly
mesh = VTKExporter.additive_part_to_polydata(part)
print(mesh.cell_data.keys())  # feed_rate, elapsed_time, deposited_volume, layer_time, ...

# Attach custom scalar data (e.g. measured temperature) per segment
mesh = VTKExporter.additive_part_to_polydata(part, custom_scalars={"temperature": temp_array})

Visualization

The gcode_reader.visualize module provides helpers for 3D toolpath visualization using PyVista:

import gcode_reader as gr

operation = gr.gcode_to_operation("my_part.gcode", machine="cead")
gr.visualize.plot_operation(operation)

Analysis

The gcode_reader.analysis module contains functions for computing statistics and event series from G-code data:

from gcode_reader import analysis

stats = analysis.gcode_dataframe_to_event_series(df)