← All Features

G-code Preprocessing

An embedded Python interpreter with 130+ API bindings that runs your script as a step of G-code generation, with structured access to every move, layer and setting. The Python runtime is bundled, so there is nothing to install.

Inside the Slice

You know the usual "post-process your G-code files at export" workflow. The slicer is done with the data. You get raw text and a regex library. Good luck.

preFlight embeds a Python interpreter in the slicing pipeline through pybind11, and your script runs as a step of G-code generation. It gets the full object structure: layers, moves, feedrates, fan speeds, coordinates, extrusion roles, volumetric flow rates, and more. There is no text to parse after the fact. The script is iterating over objects, within the slicing pipeline.

Your script runs during the slice, once the G-code exists as structured data and before the preview renders. What it reads and modifies is the G-code preFlight exports, and the time estimate, the filament statistics, the M73 progress codes and the preview are all computed from the modified result. preFlight still runs classic post-processing scripts on the exported file, but they are deprecated in favor of preprocessing.

Fan and temperature overrides are scoped to the moves a script changes: the M106 or M104 is written where the value changes, and the original is restored before the first extruding move you did not touch. Scripts have a time limit, five minutes by default, and Cancel Slicing interrupts a running script.

A Complete Example

This is the pressure-advance-per-feature sample, trimmed to its working core. Save it anywhere outside the preFlight install folder, add it to your profile's Preprocessing tab, and slice. It detects Marlin, Klipper and RepRapFirmware from the printer profile.

import preFlight
from preFlight import MoveType, ExtrusionRole

PA_VALUES = {
    ExtrusionRole.ExternalPerimeter:        0.040,
    ExtrusionRole.Perimeter:                0.050,
    ExtrusionRole.OverhangPerimeter:        0.020,
    ExtrusionRole.TopSolidInfill:           0.040,
    ExtrusionRole.SolidInfill:              0.060,
    ExtrusionRole.InternalInfill:           0.080,
    ExtrusionRole.BridgeInfill:             0.000,
    ExtrusionRole.GapFill:                  0.030,
    ExtrusionRole.SupportMaterial:          0.060,
    ExtrusionRole.SupportMaterialInterface: 0.040,
    ExtrusionRole.Skirt:                    0.050,
    ExtrusionRole.Ironing:                  0.020,
}
DEFAULT_PA = 0.050

def detect_firmware(gcode: preFlight.GCode):
    flavor = gcode.settings.gcode_flavor
    if "reprap" in flavor.lower() or "duet" in flavor.lower():
        return "reprap"
    if "klipper" in flavor.lower():
        return "klipper"
    return "marlin"

def format_command(firmware, pa_value, extruder=0):
    if firmware == "klipper":
        return f"SET_PRESSURE_ADVANCE ADVANCE={pa_value:.4f}"
    if firmware == "reprap":
        return f"M572 D{extruder} S{pa_value:.4f}"
    return f"M900 K{pa_value:.4f}"

def process(gcode: preFlight.GCode):
    firmware = detect_firmware(gcode)
    current_pa = None

    for layer in gcode.layers:
        if layer.id == 0:
            continue

        for move in layer.moves:
            if move.type != MoveType.Extrude:
                continue

            target_pa = PA_VALUES.get(move.role, DEFAULT_PA)
            if target_pa != current_pa:
                cmd = format_command(firmware, target_pa, move.extruder_id)
                gcode.insert(move.gcode_line_id, cmd, "before")
                current_pa = target_pa

130+ API Bindings

The entire 400+ setting object model (print, filament, printer values) is exposed as read-only properties with IDE autocomplete baked in. Every move exposes its type, extrusion role, position, feedrate, volumetric flow rate, extrusion width, layer height, and extruder ID. Four properties are writable and propagate directly to G-code: feedrate, filament displacement, fan speed, and temperature.

Each move also carries its distance, junction angle, effective acceleration and maximum entry speed, the same values the firmware's look-ahead planner uses. A script can tell whether a move will reach its commanded speed or spend the whole segment accelerating.

Fill region geometry is exposed per-move: the area of the fill region in mm² and the infill pattern name. This lets scripts detect small features and adjust flow or speed to prevent heat buildup in tight areas.

What Else You Can Build

preFlight ships with 20 sample scripts. These four use data that a post-processing script never sees:

Adaptive Pressure Advance

Computes PA per move from the actual post-acceleration feedrate, volumetric flow rate and junction angle, and lowers it at sharp corners to prevent decompression gouging. Emits Marlin M900, Klipper SET_PRESSURE_ADVANCE or RRF M572.

def compute_pa(move) -> float:
    base = ROLE_PA_BASE.get(move.role, PA_BASE)
    speed = move.actual_feedrate
    pa = base + SPEED_FACTOR * speed

    if move.actual_volumetric_rate > VOLUMETRIC_THRESHOLD:
        pa += VOL_FACTOR * (move.actual_volumetric_rate - VOLUMETRIC_THRESHOLD)

    pa -= corner_reduction(move.junction_angle)
    return max(PA_MIN, min(PA_MAX, pa))

Small Area Flow Compensation

Reads move.region_area to find small fill regions and reduces flow in proportion to their size, so heat buildup in a tight spot does not bulge the surface.

for layer in gcode.layers:
    for move in layer.moves:
        if 0 < move.region_area < SMALL_AREA_THRESHOLD:
            t = (SMALL_AREA_THRESHOLD - move.region_area) / range_size
            move.delta_e *= lerp(0.95, 0.85, t)
            move.feedrate *= lerp(1.0, 0.8, t)

Motion Optimizer

Uses distance, junction angle, acceleration and max entry speed to find moves that can never reach their commanded feedrate, and clamps them to the speed they will actually hit, so flow calculations and the preview reflect the real print.

v_entry = move.max_entry_speed
v_peak = sqrt(v_entry**2 + move.acceleration * move.distance)

if move.feedrate > v_peak:
    move.feedrate = v_peak

Print Analyzer

Changes nothing in the G-code. Breaks down time, filament and move counts by feature type and flags excessive travel, high retraction counts and unusually slow layers.

for role, usage in gcode.filament_by_role.items():
    print(f"  {role.name}: {usage.meters:.1f}m, {usage.grams:.1f}g")

for layer_id, z, t in slowest_layers[:10]:
    print(f"  Layer {layer_id} Z={z:.2f}mm  {format_time(t)}")

Additional Samples

The remaining sample scripts cover flow limiting per feature type, overhang speed/fan/temperature tuning, temperature tower generation from any model, height-based fan curves, first layer speed ramping, edge slowdown near bed boundaries, per-feature extrusion multipliers, per-feature M204 acceleration, per-feature jerk or junction deviation, context-aware retraction tuning, color-distance-aware purge reduction for multi-material, a layer-by-layer walk of the raw text that annotates each layer, and vectorized numpy analysis. Two more are reference scripts: an M73 progress inserter (preFlight does this natively, the sample shows the technique) and an API test that exercises every binding with a pass/fail report.

Community Scripts

The samples, a template for new scripts and the full API reference live in the preFlight-scripts repository on GitHub, alongside scripts contributed by other preFlight users. Start your own from the template there, or send yours in as a pull request: one file in a folder of its own, a header that says what it changes and what it touches outside preFlight, and every tunable value as a named constant at the top with a default that is safe to run untuned. oozeBot reads and runs every community script before merging it.

Scripts from the repository are used at your own risk. They were written for other printers and other materials. Read a script, set its values for your setup, check the preview and the exported G-code, and run a short test print before trusting it on a long one.

Full Python

Scripts have full Python access: file I/O, the network, subprocesses and third-party libraries. A few things that makes possible:

Any package installed with pip is available to a preprocessing script alongside the preFlight module.

Bundled Python Runtime

The interpreter is embedded in preFlight with a bundled Python 3.14 runtime, so there is no venv to set up, no separate Python to install, and nothing written to system directories, PATH or the registry. It works the same on a locked-down Linux distro.

For scripts that need third-party packages, a Python Console button in Preferences launches a shell with PATH configured for the bundled runtime so you can pip install directly.

Installed packages are stored in your preFlight data folder, so upgrading preFlight does not wipe them. They only need reinstalling when preFlight ships a new version of Python. Only packages that provide a prebuilt wheel can be installed, since the bundled Python has no compiler. Keep your own scripts outside the preFlight install folder: preFlight ships self-contained, and a script saved inside its resources folder does not carry over to a new version.

Per-Profile Script Management

Preprocessing is built into all three settings panels: Print Settings, Filament Settings and Printer Settings. Each has its own Preprocessing tab with an enable toggle and an ordered script list, because different presets need different scripts.

A pressure advance script belongs in your filament profile, because PA values change with material. A flow limiter might belong in your printer profile, because volumetric limits are hotend-specific. A small area flow compensation script fits in print settings, because the thresholds depend on your quality/speed tradeoffs. When you switch presets, the right scripts come with them.

Scripts from all three profiles run together at slice time. The execution order between categories is configurable in the Preprocessing tab in Preferences. If your filament scripts need to run before your print scripts, move them up. The same Preferences tab provides a Python Console button that launches a shell with PATH configured for the bundled runtime, so you can pip install numpy, requests, or any other package your scripts need.

Script validation rejects invalid Python identifiers on add, blocks duplicate paths within the same profile, and deduplicates across profiles at slice time. Errors surface as breadcrumb notifications without aborting the slice.

IDE Support

A preFlight.py type stub ships with every release. Drop it next to your script and any Python IDE with type hint support (VS Code + Pylance, PyCharm, etc.) gives you full autocomplete across all 130+ API bindings, all 400+ slicer settings, and every move property with types.

Security Model

Preprocessing scripts are Python programs with full system access, the same trust model as post-processing scripts in other slicers. preFlight requires explicit consent before any scripts can run. The first time preprocessing is enabled, a security warning explains the risk and asks for confirmation. Consent can be revoked at any time via Preferences. 3MF project files can store script references (file paths only, not embedded code). When a project references preprocessing scripts, preFlight warns the user and asks for explicit confirmation before adding them to the configuration.

The API at a Glance

ObjectKey Properties
gcodelayers, moves, settings (400+), bed_shape, extruder config (count, colors, diameters, densities), time estimates, cost data, active preset names, filament usage by role/extruder/color, custom events, performance metrics, conflict detection, annotation (r/w), line-level read/rewrite/insert, find_line, find_moves, remove_moves
layerid, z, height, moves, time, prepend/append G-code, filter by type/role, extrusion_length, travel_distance
movetype, role, x/y/z, extruder_id, gcode_line_id, feedrate (r/w), delta_e (r/w), fan_speed (r/w), temperature (r/w), width (r/w preview), height (r/w preview), annotation (r/w), actual_feedrate, mm3_per_mm, volumetric_rate, actual_volumetric_rate, time, distance, junction_angle, acceleration, max_entry_speed, region_area, fill_pattern