Clean and renumber a G-code program (Python)
A program that's been hand-edited a dozen times ends up with blank lines, stale N-numbers from three revisions ago, and coordinates that mix X40 with X40.000 in the same file. This script fixes all three in one pass: strips blank lines, renumbers every N-block from a clean starting point, and normalizes X/Y/Z/I/J/K/R words to always carry a decimal point — the same formatting discipline the bolt-circle generator applies to its own output, run backwards over a file someone else wrote.
Before you run it
- Python 3.8+ (standard library only)
- A G-code program to clean — try the demo above with your own pasted program
The code
Run it here — no install
Loads a real Python interpreter in your browser (Pyodide, one-time ~10 MB download) and calls clean_lines() from the code above. Paste your own program or try the messy sample below.
"""Clean and renumber a G-code program: strip blank lines, renumber N-blocks,
and normalize decimal formatting on X/Y/Z/I/J/K/R words.
Usage: python gcode_clean.py input.nc --out cleaned.nc
python gcode_clean.py input.nc --start 10 --step 10
"""
import argparse
import re
WORD_RE = re.compile(r'([A-Z])(-?\d+\.?\d*)')
def clean_lines(lines, start=10, step=10, strip_comments=False):
"""Renumber N-blocks and normalize decimals. Returns the cleaned lines."""
out = []
n = start
for raw in lines:
line = raw.strip()
if not line:
continue
if strip_comments and (line.startswith("(") or line.startswith(";")):
continue
# Drop any existing N-word so renumbering never collides with it
line = re.sub(r'^N\d+\s*', '', line)
def normalize(match):
letter, value = match.groups()
if letter in "XYZIJKR" and "." not in value:
value += "."
return f"{letter}{value}"
line = WORD_RE.sub(normalize, line)
if line.startswith("(") or line.startswith(";") or line.startswith("O"):
out.append(line) # comments/program numbers keep their own line, no N-word
else:
out.append(f"N{n} {line}")
n += step
return out
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("file", help="G-code program to clean")
ap.add_argument("--out", help="output file (default: print to stdout)")
ap.add_argument("--start", type=int, default=10, help="first N-number")
ap.add_argument("--step", type=int, default=10, help="N-number increment")
ap.add_argument("--strip-comments", action="store_true",
help="drop comment-only lines instead of keeping them")
args = ap.parse_args()
with open(args.file) as fh:
lines = fh.readlines()
cleaned = clean_lines(lines, args.start, args.step, args.strip_comments)
result = "\n".join(cleaned) + "\n"
if args.out:
with open(args.out, "w") as fh:
fh.write(result)
print(f"{len(cleaned)} lines written to {args.out}")
else:
print(result, end="")
if __name__ == "__main__":
main()What you get
$ python gcode_clean.py messy.nc
O1001 (BOLT CIRCLE - MESSY EXPORT)
N10 G90 G94 G17 G21
N20 G54
N30 T1 M6
N40 S2400 M3
N50 G43 H01 Z25.
N60 G98 G81 X40. Y0. Z-12. R2. F120
N70 X28.284 Y28.284
(RETRACT)
N80 G80
N90 G0 Z25.
N100 M5
N110 M30
How it works
- Any existing N-word is stripped from the front of the line before renumbering — so a file with stale N5/N999 numbers from a previous edit gets clean sequential numbers, not a second N-word appended after the old one.L25–26
normalize()only touches X/Y/Z/I/J/K/R words, and only when they're missing a decimal point —X40becomesX40., butS2400andF120are left alone, because a bare integer feed rate or RPM means exactly what it says.L28–32- Comments and the
O-number line keep their place in the file but never get an N-word of their own — matching how real programs are formatted, where the header and inline notes aren't numbered blocks.L36–40 --strip-commentsis opt-in, not the default — deleting comments destroys documentation a machinist may need; renumbering and reformatting are the safe defaults, dropping notes is a deliberate choice.
Gotchas & honest limits
- This is a line-oriented text cleaner, not a G-code parser — it will also "normalize" a letter-number pair that happens to appear inside a parenthetical comment (harmless, since comments aren't executed, but worth knowing).
- Renumbering breaks any internal N-word references (rare in a straight linear post, more common in hand-edited macros with jumps) — check for GOTO-style references before renumbering a macro-heavy file.
- Decimal normalization doesn't know or care whether the program is inches or mm — it just standardizes formatting either way.
- Reads the whole file into memory — fine for anything a person would hand-edit; not the tool for a multi-gigabyte five-axis surface job.
Goes deeper
Want this adapted to your shop — or built into a real tool?
Samples are the free 80%. The last 20% is the part I do for a living.