OK, I don’t want to do that conversationally again, so I certainly want a generator. It’s probably quicker to write one than to search for one that works the way I want.
$ ./generate-surface.py --help
usage: generate-surface.py [-h] --x-max X_MAX --y-max Y_MAX --pitch PITCH --feed
FEED [--z-depth Z_DEPTH] [--safe SAFE] [--only-x]
[--only-y] [--boustrophedonically] [--monotonically]
Generate gcode for spoilboard surfacing.
options:
-h, --help show this help message and exit
--x-max X_MAX, -x X_MAX
X working width
--y-max Y_MAX, -y Y_MAX
Y working width
--pitch PITCH, -p PITCH
Pitch of passes/step over
--feed FEED, -f FEED Horizontal feed
--z-depth Z_DEPTH, -z Z_DEPTH
Z depth to cut (0)
--safe SAFE, -s SAFE Safe Z height (3)
--only-x, -w Surface only in X (Default both)
--only-y, -d Surface only in Y (Default both)
--boustrophedonically, -b
Run boustrophodonically instead of default monotonically
--monotonically, -m Run monotonically (default)
#!/usr/bin/python
import argparse
parser = argparse.ArgumentParser(description='Generate gcode for spoilboard surfacing.')
parser.add_argument('--x-max', '-x', type=int, required=True, help='X working width')
parser.add_argument('--y-max', '-y', type=int, required=True, help='Y working width')
parser.add_argument('--pitch', '-p', type=int, required=True, help='Pitch of passes/step over')
parser.add_argument('--feed', '-f', type=int, required=True, help='Horizontal feed')
parser.add_argument('--z-depth', '-z', type=float, default=0, help='Z depth to cut (0)')
parser.add_argument('--safe', '-s', type=float, default=3, help='Safe Z height (3)')
parser.add_argument('--only-x', '-w', default=False, action='store_true', help='Surface only in X (Default both)')
parser.add_argument('--only-y', '-d', default=False, action='store_true', help='Surface only in Y (Default both)')
parser.add_argument('--boustrophedonically', '-b', default=False, action='store_true', help='Run boustrophodonically instead of default monotonically')
parser.add_argument('--monotonically', '-m', dest='boustrophedonically', action='store_false', help='Run monotonically (default)')
args = parser.parse_args()
v = vars(args)
step_pitch = {False: args.pitch, True: args.pitch*2}[args.boustrophedonically]
def steps(end):
s = [x for x in range(0, end, step_pitch)]
if s[-1] != end:
# end not a multiple of pitch
s.append(end)
return s
x_steps = steps(args.x_max)
y_steps = steps(args.y_max)
print('G0 Z{safe}'.format(**v))
print('G0 X0 Y0')
print('G1 Z{z_depth} X{pitch} F{feed}'.format(**v))
print('G1 Z{z_depth} X0'.format(**v))
if not args.only_y:
for y in y_steps:
print('G1 Y{}'.format(y))
print('G1 X{x_max}'.format(**v))
if args.boustrophedonically:
print('G1 Y{}'.format(y+args.pitch))
print('G1 X0')
else:
print('G0 X0')
print('G0 Y0')
if not args.only_x:
if not args.only_y:
steps = x_steps[1:] # X0 step already effectively run
else:
steps = x_steps
for x in steps:
print('G1 X{}'.format(x))
print('G1 Y{y_max}'.format(**v))
if args.boustrophedonically:
print('G1 X{}'.format(x+args.pitch))
print('G1 Y0')
else:
print('G0 Y0')
print('G0 Z{safe} X0 Y0'.format(**v))
This works only if you have Python installed.
This intentionally does rapids over already-cut surface. You can change a few G0
to G1
if you don’t like that and it will then honor the feed rate for everything. I didn’t add a vertical feed rate because the ease-down should be dominated by the pitch anyway, so plunge rate isn’t a concern. I think.
Update from the original version: While the default is still monotonic, I added a boustrophedonic option and an option to run only X-major (side to side) and Y-major (front to back) for quicker cleanup surfacing. I even use that with a 50mm or so pitch to run the vacuum across the surface for quick cleanup.
Update: Now prints useful message if invoked without arguments.