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
(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
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
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)
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.