There are two kinds of CNC programs. One kind has real geometry — surfaces, cutter comp around a contour, 5-axis motion — and belongs to CAM, full stop. The other kind is arithmetic wearing a G-code costume: bolt circles, grid drilling, facing passes, the same fixture plate at six sizes. Shops burn real hours pushing the second kind through CAM — model, toolpath, post, repeat for every variant — when the program is 30 lines of Python away. If you can write a for loop, you can retire that whole workflow.
When generation wins (and loses)
- Wins: part families. Same features, different numbers. One script, a dictionary of sizes, done — the classic alternative to macro-variable programming on the control, with the advantage that you can version-control and test it off the machine.
- Wins: patterns. Bolt circles, grids, engraving serials, any "repeat this move N times with math" program.
- Wins: fixtures and soft jaws — simple prismatic cuts you make weekly with slightly different numbers.
- Loses: anything with cutter comp around real contours, surfacing, or simultaneous multi-axis. That's CAM's job. Generating it by hand is how you re-discover why CAM exists.
A real generator: bolt-circle drilling
import math
def bolt_circle(cx, cy, radius, holes, z_depth, feed, rpm):
"""G81 bolt-circle drilling, absolute, metric, G54."""
g = []
g.append("O1001 (BOLT CIRCLE - GENERATED)")
g.append("G90 G94 G17 G21")
g.append("G54")
g.append("T1 M6")
g.append(f"S{rpm} M3")
g.append("G0 Z25.0")
for i in range(holes):
a = math.radians(i * 360.0 / holes)
x = cx + radius * math.cos(a)
y = cy + radius * math.sin(a)
if i == 0:
g.append(f"G98 G81 X{x:.3f} Y{y:.3f} "
f"Z{-z_depth:.3f} R2.0 F{feed:.0f}")
else:
g.append(f"X{x:.3f} Y{y:.3f}") # G81 is modal
g.append("G80")
g.append("G0 Z25.0")
g.append("M5")
g.append("M30")
return "\n".join(g)
print(bolt_circle(cx=0, cy=0, radius=40, holes=8,
z_depth=12, feed=120, rpm=2400))Two details carry most of the craft. Formatting: always emit coordinates with a fixed format like :.3f — floating-point stragglers like X39.99999999999999 are how generated code gets a bad name. Modality: the G81 cycle stays active, so holes after the first need only coordinates; knowing your dialect's modal behavior is the difference between generated code that reads like a machinist wrote it and code that reads like a robot sneezed.
The safety rules are not optional
Generated code fails differently from CAM code — a sign error puts every hole in the wrong place with perfect confidence. So: 1) keep a fixed, human-reviewed header/footer template (safe start-up line, tool change, coolant, retract) and only generate the middle; 2) dry-run above the part or in the machine's graphics mode, every new script, every time; 3) never math your way to a rapid move below clearance — generate rapids only at your retract plane; 4) review the first output line by line like a stranger wrote it. After that, the script is more trustworthy than hand-typing, because it can't have a Tuesday.
Test the output before the machine does
The beautiful thing about generation is that the output is text — you can check it with code too. Parse the file and assert every Z is above clearance during rapids, every feed is in range, the program ends with M30. I built a free G-code time auditor that reads a program and breaks down where the minutes go — run your generated file through it and you'll often spot dumb sequencing (like retracting to full height between adjacent holes) before the machine ever sees it. That kind of audit is also exactly where cycle time hides in hand-written programs.
CAM answers "how do I cut this shape?" A generator answers "how do I never program this family again?" Different question, different tool.
Start with one family you program repeatedly, wrap it in a function like the one above, and put it in version control next to a test. (The complete bolt-circle generator — peck drilling, CLI flags, start angle — is free in the code library.) If you'd like help building a generator for your ugliest part family — or a validator for the output — get in touch.


