-
Notifications
You must be signed in to change notification settings - Fork 24
Description
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()