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:
Pre-processing (
gcode_reader.preprocess) — resolves variables, calculations, and macros in the raw G-code text.Reading (
gcode_reader.read) — tokenizes pre-processed lines into raw command strings.Parsing (
GcodeParser) — converts raw strings into typedCommandobjects (moves, config changes, dwells, etc.) which describe the instruction sent to the machine.Machine emulation (
Machine) — emulates commands against a machine model, tracking state (position, feed rate, active tool) and applying motion profiles.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 |
|---|---|
|
Linear movement to a location at a feed rate |
|
Arc movement to a location about a center point |
|
Specifies a deposit volume or extrusion rate for a toolhead |
|
Linear movement combined with extrusion (typical FFF/LFAM deposition move) |
|
Arc movement combined with extrusion |
|
Specifies a spindle rate for a milling toolhead |
|
Linear movement combined with a milling spindle rate |
|
Arc movement combined with a milling spindle rate |
|
Pause at the current location for a specified duration (G4) |
|
Dwell while extruding (stationary deposition) |
|
Feed-rate-only configuration update |
|
Generic machine configuration command (G/M-code with no dedicated subclass) |
|
A G-code configuration command |
|
An M-code configuration command |
|
Switch to absolute positioning mode (G90) |
|
Switch to incremental positioning mode (G91) |
|
Set work coordinate system origin (G54) |
|
Move in machine coordinate space (G53) |
|
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 |
|---|---|---|
|
|
location, feed rate, elapsed time, distance |
|
|
deposited volume, bead area, extruder type |
|
|
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:
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 largerjunction_deviationvalue lets the machine carry more speed through corners; a smaller value forces tighter slowdowns.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).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 |
|---|---|
|
Moves at a fixed speed with no ramp |
|
Linear acceleration from initial to final velocity |
|
Accelerate, cruise, decelerate — the default for most machines |
|
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)