# 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: ```python # 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:** ```python 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. ```python 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: ```python 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: ```python event_series = gr.gcode_dataframe_to_event_series(df) ``` Or export directly from a G-code file: ```python 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. ```python 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](https://pyvista.org/): ```python 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: ```python from gcode_reader import analysis stats = analysis.gcode_dataframe_to_event_series(df) ```