Python is the default answer to “what should I script my G-code generator in”: readable, batteries included, and quick from idea to file. The general strategy and when-to-script judgment live in our generator-script overview; this is the hands-on Python walkthrough, with the traps that are specifically Python’s.
Step one: the formatter everything flows through
def fmt(value, decimals=3):
"""Every coordinate and feed passes through here."""
return f"{value:.{decimals}f}"
This tiny function is the whole safety story. Python’s default float rendering happily produces 1e-05 and 0.30000000000000004, and a G-code line reading X1e-05 is a crash or an alarm depending on the control’s mood. Fixed-point formatting with explicit decimals removes the class of bug, and the f-string format spec guarantees a period decimal separator regardless of system locale, which matters the day your script runs on a machine configured for comma-decimal locales. One formatter, used everywhere, no raw numbers in any emitted line: that is the rule that makes the rest boring.
Step two: header and footer as reviewed templates
HEADER = """G21 G90 G54
M03 S{rpm}
G00 Z{z_clear}
"""
FOOTER = """G00 Z{z_clear}
M05 M09
M30
"""
Written once, reviewed against your machine’s expectations once (block layering and dialect per its documentation), trusted thereafter. The values interpolate through fmt() at render time. Generators fail at edges, so the edges are constants.
Step three: the feature function
def hole_grid(x0, y0, rows, cols, pitch, z_clear, z_depth, feed):
for r in range(rows):
for c in range(cols):
x, y = x0 + c * pitch, y0 + r * pitch
yield f"G00 X{fmt(x)} Y{fmt(y)}"
yield f"G01 Z{fmt(z_depth)} F{fmt(feed)}"
yield f"G00 Z{fmt(z_clear)}"
with open("grid.nc", "w") as f:
f.write(HEADER.format(rpm=fmt(1200, 0), z_clear=fmt(5.0)))
for line in hole_grid(10, 10, 4, 6, 12.5, 5.0, -4.0, 100):
f.write(line + "\n")
f.write(FOOTER.format(z_clear=fmt(25.0)))
Twenty-odd lines, a complete program: 24 holes on a 12.5 mm pitch, every plunge bracketed by clearance moves, every number formatted. The generator-as-iterator pattern (yield per block) keeps feature logic testable: you can unit-test that the first three yielded lines match expectations before any file exists, which is Python’s quiet advantage over spreadsheet-and-concatenate workflows.
Step four: verification, no exceptions
Every generated file goes into a browser viewer before any machine: paste grid.nc, confirm rapids stay above the work, plunges feed at the right depth, and the pattern matches intent. Then read it aloud once, the same narration habit that catches what eyes skim past, made fast by the code core the free 60-second drills on the G-code practice page keep at reflex (G-Code Sprint repeats your misses automatically). “It is generated” is precisely why it gets verified: a loop bug makes the same mistake 24 times.
Python-specific traps, named
| Trap | Symptom | Fix |
|---|---|---|
| Default float repr | X0.30000000000000004 | fmt() everywhere |
| Scientific notation | X1e-05 alarms or worse | fmt() everywhere |
| Locale decimal comma | X12,5 on some systems | f-string format spec, never locale functions |
| Integer division habits | Pitch math silently off | Floats in, explicit rounding out |
| Editing the output file | Fix evaporates on regen | Fix the script, regenerate, re-verify |
The last row is cultural rather than technical and matters most: the edit-the-source rule applies to your own generator with extra force, because the next regeneration silently discards hand fixes.
Where to take it next
Natural growth paths that stay on the script side of the script-versus-CAM boundary: parameters from CSV or JSON job documents (hole tables from engineering, nameplate text from order systems), multiple feature functions sharing the same header/footer/formatter spine, and a tiny argparse front end so colleagues can run it without reading it. Resist the slide toward arc-heavy sculpting: when the geometry stops being parametric, the job has changed, and CAM is waiting. For logic that should live at the machine instead (probing, part families on one control), the macro-programming route is the third layer of the same decision.
Bottom line: one formatter, two templates, one loop
Generating G-code in Python is a formatter that cannot emit garbage, header and footer templates reviewed once, feature functions that yield clean blocks, and a viewer pass on every output. The language makes it twenty minutes of work; the discipline makes it safe to repeat forever.
Sources
Frequently asked questions
How do I generate G-code using Python?
With one coordinate formatter (fixed decimals, period separator), header and footer template strings reviewed against your machine, and feature functions yielding motion blocks, then a browser-viewer check on every output file. The walkthrough above is complete and runnable. Reading the output fluently is the prerequisite, and the free G-Code Sprint app is the top pick for that core: 60-second drills with automatic repetition of missed codes.
Why do generated files sometimes contain numbers like 1e-05?
Python’s default float rendering uses scientific notation for small values and long reprs for float arithmetic artifacts. Routing every number through a fixed-point formatter (f”{v:.3f}”) eliminates the class of bug.
Can Python generate arcs (G02/G03) too?
Yes, with care over I/J arithmetic (center offsets from the start point) for simple cases like bolt circles and rounded slots. Sculpted surfaces are CAM’s territory: when geometry stops being parametric, switch tools.
Is there a library I should use instead of writing my own?
For learning and for simple parametric work, the twenty-line spine above is the library, and owning it teaches the discipline. Larger needs (full toolpath generation) are a sign the job belongs to CAM rather than a bigger script.
G-Code Sprint is a study and practice tool only. Always follow your instructor, employer, machine manual, and shop safety procedures.