puthon script
The script inserts a small Z lift, moves to the target X, then returns Z so the move doesn't drag through the print. Defaults: move to X=0 every 10 layers with a 5 mm lift and a 6000 mm/min travel feed.
#!/usr/bin/env python3 """ insert_move_every_n_layers.py
Usage (PrusaSlicer post-processing): prusaslicer will invoke the script; it usually passes input and output filenames. Example args in PrusaSlicer: --x 0 --every 10 --lift 5 --feed 6000
This script finds lines like ";LAYER:123" and after every Nth layer inserts a short safe sequence to lift Z, move to X position, then restore Z.
Default values: x=0.0, every=10, lift=5.0, feed=6000 """
import sys import argparse import re
def parse_args(): p = argparse.ArgumentParser(description="Insert a move to X every N layers in G-code") p.add_argument("--x", type=float, default=0.0, help="X coordinate to move to (mm)") p.add_argument("--every", type=int, default=10, help="Do it every N layers (use 1 for every layer)") p.add_argument("--lift", type=float, default=5.0, help="Relative Z lift before moving (mm)") p.add_argument("--feed", type=float, default=6000, help="Feedrate for travel move (mm/min)") p.add_argument("--marker", type=str, default=";MOVE_INSERTED", help="Marker comment to avoid double-insertions") p.add_argument("infile", nargs="?", default=None, help="Input gcode file (optional, PrusaSlicer usually passes it)") p.add_argument("outfile", nargs="?", default=None, help="Output gcode file (optional)") return p.parse_args()
def make_move_block(x, lift, feed, marker): # Use relative moves for lift and return so we don't need current Z value. # Sequence: # ; marker # G91 ; relative mode # G1 Z{lift} F3000 # G90 ; absolute mode # G1 X{x} F{feed} # G91 # G1 Z-{lift} F3000 # G90 # You may want to tweak feed and retracts for your setup. lines = [ f"{marker} ; inserted by insert_move_every_n_layers.py", "G91", f"G1 Z{lift:.3f} F3000 ; lift before move", "G90", f"G1 X{x:.3f} F{feed:.0f} ; travel to X", "G91", f"G1 Z-{lift:.3f} F3000 ; lower back down", "G90", "" # blank line for readability ] return "\n".join(lines)
def main(): args = parse_args()
# Input / output file handling
if args.infile and args.outfile:
infile = args.infile
outfile = args.outfile
elif args.infile and not args.outfile:
# If only one file provided, overwrite it safely
infile = args.infile
outfile = infile + ".tmp"
else:
# When PrusaSlicer runs the script it usually supplies input and output file paths.
# But to be safe, read stdin and write stdout if no files provided.
infile = None
outfile = None
content = None
if infile:
with open(infile, "r", encoding="utf-8") as f:
content = f.read()
else:
content = sys.stdin.read()
# Avoid double-inserting by checking for marker presence
if args.marker and args.marker in content:
# Already ran (or file already contains marker) — do nothing and output original
out_text = content
else:
# Process content line by line
out_lines = []
layer_re = re.compile(r"^;LAYER:(\s*\d+)", re.IGNORECASE)
for line in content.splitlines():
out_lines.append(line)
m = layer_re.match(line)
if m:
try:
layer_num = int(m.group(1))
except:
# fallback: skip if can't parse
continue
if args.every > 0 and (layer_num % args.every) == 0:
# skip inserting at layer 0 (optional) — many people want to skip layer 0
# If you want to run at layer 0 too, remove the next condition.
if layer_num == 0:
continue
move_block = make_move_block(args.x, args.lift, args.feed, args.marker)
out_lines.append(move_block)
out_text = "\n".join(out_lines) + "\n"
if outfile:
with open(outfile, "w", encoding="utf-8") as f:
f.write(out_text)
# If we created a temp file (infile.tmp), replace original
if args.infile and not args.outfile:
import os
os.replace(outfile, infile)
else:
sys.stdout.write(out_text)
if name == "main": main()