DEV Community

cy cy
cy cy

Posted on

Your slicer is done, but your G-code isn't: three post-processing passes that fix real prints

Slicers output a generic approximation: they know your printer's name, not its personality. The vibration on your worn X rails, the part-cooling duct you printed yourself, the PETG that strings at exactly your shop's humidity — none of that is in the slicer's model of the world.

The usual answer is "change the profile settings," but some fixes are positional: they apply to specific layers, specific moves, specific segments. That means post-processing the G-code. Here are the three passes that have earned their keep, with the core Python for each. All of them work line-by-line on the sliced output, so they compose cleanly with any slicer.

Pass 1: Z-hop, with per-segment control

Z-hop prevents the nozzle from dragging across a finished surface during travel. But a hop applied everywhere burns blob marks into top surfaces (the hop-down re-melts them) and adds hours to tall prints.

The fix: enable hops only where they help. The trap everyone hits: when you insert lines into G-code read via readlines(), every line already ends with \n — your inserted lines must carry their own newline or the next instruction gets glued onto yours:

def with_hops(lines, hop_mm=0.4):
    out = []
    z = None
    for raw in lines:
        if raw.startswith("G1 ") and " Z" in raw:
            z = float(raw.split(" Z")[1].split(" ")[0])
            out.append(f"G1 Z{z + hop_mm:.3f} F3000 ; hop up\n")
            out.append(raw)
            out.append(f"G1 Z{z:.3f} F3000 ; hop down\n")
        else:
            out.append(raw)
    return out
Enter fullscreen mode Exit fullscreen mode

(In real use you gate this on whether the move is a travel move — G0 or a G1 without extrusion — not on every Z change.)

Pass 2: A fan-speed floor

Many profiles drop the part fan to 0% for the first layers (correct — adhesion) and then forget the floor at the other end: bridges and small perimeters need maximum cooling, and some profiles ask for 40% where your geometry needs 100%.

A floor pass enforces "from layer N onward, never below S%":

def with_fan_floor(lines, from_layer=3, min_pct=60):
    out, layer = [], 0
    for raw in lines:
        if raw.startswith(";LAYER:") or raw.startswith("; layer"):
            layer += 1
        if raw.startswith("M106") and layer >= from_layer:
            s = int(raw.split("S")[1].split(" ")[0]) if "S" in raw else 255
            raw = f"M106 S{max(s, int(255 * min_pct / 100))} ; fan floor\n"
        out.append(raw)
    return out
Enter fullscreen mode Exit fullscreen mode

Note the parser assumptions: layer markers differ between Cura (;LAYER:), PrusaSlicer (; layer_change) and Orca — handle the ones your slicer emits, and verify on real output, not on memory.

Pass 3: Speed scaling with a cap

First prints on a new machine: scale everything down. Later: you discover your printer's actual speed ceiling is lower than the profile's ambition. Both are a global multiplier with a cap on absolute values:

def scale_speeds(lines, mult=0.7, cap_mm_min=6000):
    out = []
    for raw in lines:
        if raw.startswith(("G0 ", "G1 ")) and " F" in raw:
            head, rest = raw.split(" F", 1)
            f_tok = rest.split(" ")[0]
            f = min(int(float(f_tok) * mult), cap_mm_min)
            raw = f"{head} F{f} {rest[len(f_tok) + 1:]}"
        out.append(raw)
    return out
Enter fullscreen mode Exit fullscreen mode

The habit that saves prints

Whatever you post-process, re-read the result with a G-code viewer (or your slicer's preview) before it touches the printer. A malformed edit doesn't usually fail loudly — it just does something almost right for 6 hours and then destroys the print at hour 7.

I ended up packaging these three passes (plus print-time re-estimation and stats) into a small zero-dependency CLI: G-code Tuner. It refuses to write output it can't parse cleanly, which is the part that actually matters at 2 AM.

If you want the pre-print half of the story — checking the mesh itself before slicing — the five checks are free and open source in stl-precheck, and the full auto-fix toolkit is Mesh Rescue.

Same rule as always: preview the modified G-code before you commit a long print to it.

Top comments (1)

Collapse
 
alexshev profile image
Alex Shev •

The warning to inspect generated G-code in a viewer is exactly the right boundary for post-processing. I would make each pass emit a compact change summary—layers touched, feed-rate caps applied, and parser markers recognized—so a failed print can be traced to a transformation instead of a vague script version.