#!/usr/bin/python
#
# Copyright (C) 2020 abetusk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
#
import sys
import os
import numpy
import random
import getopt
import re
import scipy
import numpy as np
from scipy.interpolate import griddata
import grbl
from termcolor import colored, cprint
DEFAULT_FEED_RATE = 60
DEFAULT_DEVICE = "/dev/ttyACM0"
unit = "mm"
cur_x, cur_y, cur_z = 0, 0, 0
z_pos = 'up'
dry_run = False
z_threshold = 0.0
z_plunge_inch = -0.004
z_plunge_mm = z_plunge_inch * 25.4
tty_device = DEFAULT_DEVICE
output = None
verbose = True
def usage(ofp = sys.stdout):
ofp.write( "\nDo a height probe, interploate GCode file then execute job\n")
ofp.write( "\nusage:\n")
ofp.write( " -g gcode file\n")
ofp.write( " [-m ] height map\n")
ofp.write( " [-O ] output height map file (default stdout)\n")
ofp.write( " [-D] dry run (do not connect to GRBL)\n")
ofp.write( " [-z ] z threshold (default to " + str(z_threshold) + ")\n")
ofp.write( " [-p ] amount under height sensed part to plunge (default " + str(z_plunge_mm) + "mm)\n")
ofp.write( " [-T ] use \n")
ofp.write( " [-h|--help] help (this screen)\n")
ofp.write( "\n")
gcode_file = None
height_map_file = None
out_height_map_file = None
try:
opts, args = getopt.getopt(sys.argv[1:], "hm:g:z:Dp:O:T:", ["help", "output="])
except getopt.GetoptError, err:
print str(err)
usage()
sys.exit(2)
for o, a in opts:
if o == "-g":
gcode_file = a
elif o in ("-h", "--help"):
usage(sys.stdout)
sys.exit(0)
elif o == "-m":
height_map_file = a
elif o == "-z":
z_threshold = float(a)
elif o == "-p":
z_plunge_mm = float(a)
elif o == "-D":
dry_run = True
elif o == "-O":
out_height_map_file = a
elif o == "-T":
tty_device = a
else:
assert False, "unhandled option"
if gcode_file is None:
sys.stderr.write("Provide gcode file\n")
usage(sys.stderr)
sys.exit(-1)
pnts = []
pnts_xy = []
pntz_z = []
def read_gcode_file(gcode_filename):
cur_x = 0.0
cur_y = 0.0
xvalid = False
yvalid = False
res = {
"lines":[],
"status":"init",
"error":"",
"min_x":0.0,
"min_y":0.0,
"max_x":0.0,
"max_y":0.0
}
lines = []
with open(gcode_filename, "r") as gc:
for line in gc:
line = line.strip()
line = line.rstrip('\n')
res["lines"].append(line)
m = re.match('^\s*(\(|;)', line)
if m: continue
m = re.match('.*[xX]\s*(-?\d+(\.\d+)?)', line)
if m:
cur_x = float(m.group(1))
if not xvalid:
res["min_x"] = cur_x
res["max_x"] = cur_x
xvalid = True
if cur_x < res["min_x"]: res["min_x"] = cur_x
if cur_x > res["max_x"]: res["max_x"] = cur_x
m = re.match('.*[yY]\s*(-?\d+(\.\d+)?)', line)
if m:
cur_y = float(m.group(1))
if not yvalid:
res["min_y"] = cur_y
res["max_y"] = cur_y
yvalid = True
if cur_y < res["min_y"]: res["min_y"] = cur_y
if cur_y > res["max_y"]: res["max_y"] = cur_y
res["status"] = "ok"
return res
def interpolate_gcode(gcode, pnts_xy, pnts_z, _feed=DEFAULT_FEED_RATE):
unit = "mm"
cur_x, cur_y, cur_z = 0, 0, 0
z_pos = 'up'
z_pos_prv = z_pos
z_threshold = 0.0
z_plunge_inch = -0.006
z_plunge_mm = z_plunge_inch * 25.4
lines = []
z_leeway = 1
z_ub = pnts_z[0]
g1_feed = _feed
for idx in range(len(pnts_z)):
if z_ub < pnts_z[idx]: z_ub = pnts_z[idx]
z_ub += z_leeway
for line in gcode["lines"]:
line = line.strip()
is_move = 0
l = line.rstrip('\n')
# skip comments
# assumes comments encased in parens all on one line
#
m = re.match('^\s*(\(|;)', l)
if m:
lines.append(l)
continue
m = re.match('^\s*[gG]\s*(0*\d*)([^\d]|$)', l)
if m:
tmp_mode = m.group(1)
if re.match('^0*20$', tmp_mode):
unit = "inch"
elif re.match('^0*21$', tmp_mode):
unit = "mm"
m = re.match('^\s*[gG]\s*(0*[01])[^\d](.*)', l)
if m:
g_mode = m.group(1)
l = m.group(2)
m = re.match('.*[xX]\s*(-?\d+(\.\d+)?)', l)
if m:
is_move = 1
cur_x = m.group(1)
m = re.match('.*[yY]\s*(-?\d+(\.\d+)?)', l)
if m:
is_move = 1
cur_y = m.group(1)
m = re.match('.*[zZ]\s*(-?\d+(\.\d+)?)', l)
if m:
is_move = 1
cur_z = m.group(1)
if ( float(cur_z) >= z_threshold ):
z_pos = 'up'
else:
z_pos = 'down'
if is_move and (not g_mode):
return None
if not is_move:
lines.append(l)
continue
if (z_pos == 'up'):
lines.append("G" + str(g_mode) + " Z{0:.8f}".format(z_ub))
elif (z_pos == 'down'):
interpolated_z = griddata(pnts_xy, pnts_z, (cur_x, cur_y), method='linear')
if np.isnan(interpolated_z):
sys.stderr.write("ERROR: NaN at " + str(cur_x) + " " + str(cur_y))
sys.stdout.write("ERROR: NaN at " + str(cur_x) + " " + str(cur_y))
sys.stderr.flush()
sys.stdout.flush()
sys.exit(-5)
if unit == "inch":
z_plunge = z_plunge_inch
elif unit == "mm":
z_plunge = z_plunge_mm
else:
#print "ERROR: unit improperly set"
return None
interpolated_z += z_plunge
x_f = float(cur_x)
y_f = float(cur_y)
if z_pos_prv == "up":
lines.append("G0 X{0:.8f}".format(x_f) + " Y{0:.8f}".format(y_f) + " Z{0:.8f}".format(z_ub))
#print "G" + g_mode, "X{0:.8f}".format(x_f), "Y{0:.8f}".format(y_f), "Z{0:.8f}".format(interpolated_z)
#lines.append("G" + str(g_mode) + " X{0:.8f}".format(x_f) + " Y{0:.8f}".format(y_f) + " Z{0:.8f}".format(interpolated_z))
lines.append("G" + str(g_mode) + " X{0:.8f}".format(x_f) + " Y{0:.8f}".format(y_f) + " Z{0:.8f}".format(interpolated_z) + " F{0:.8f}".format(g1_feed))
z_pos_prv = z_pos
return lines
_gc = read_gcode_file(gcode_file)
pnts = []
if not dry_run:
grbl.setup(tty_device)
grbl.send_initial_command("")
sys.stdout.write("\n#### ")
cprint("READY TO CUT, REMOVE PROBE AND PRESS ENTER TO CONTINUE", "red", attrs=['blink'])
sys.stdout.flush()
sys.stdin.readline()
if not dry_run:
grbl.send_command("G1Z5")
grbl.send_command("M42")
for line in _gc["lines"]:
line = line.strip()
if len(line)==0: continue
if line[0] == '#': continue
if line[0] == '(': continue
print("## sending:", line)
sys.stdout.flush()
r = grbl.send_command(line)
print "### got:", r
sys.stdout.flush()
grbl.send_command("M43")