cnc3040.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. #!/usr/bin/python
  2. #
  3. # Copyright (C) 2020 abetusk
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU Affero General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU Affero General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Affero General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #
  18. import sys
  19. import os
  20. import numpy
  21. import random
  22. import getopt
  23. import re
  24. import scipy
  25. import numpy as np
  26. from scipy.interpolate import griddata
  27. import grbl
  28. from termcolor import colored, cprint
  29. DEFAULT_FEED_RATE = 60
  30. DEFAULT_DEVICE = "/dev/ttyACM0"
  31. unit = "mm"
  32. cur_x, cur_y, cur_z = 0, 0, 0
  33. z_pos = 'up'
  34. dry_run = False
  35. z_threshold = 0.0
  36. z_plunge_inch = -0.004
  37. z_plunge_mm = z_plunge_inch * 25.4
  38. tty_device = DEFAULT_DEVICE
  39. output = None
  40. verbose = True
  41. def usage(ofp = sys.stdout):
  42. ofp.write( "\nDo a height probe, interploate GCode file then execute job\n")
  43. ofp.write( "\nusage:\n")
  44. ofp.write( " -g <gcode file> gcode file\n")
  45. ofp.write( " [-m <height map>] height map\n")
  46. ofp.write( " [-O <out height>] output height map file (default stdout)\n")
  47. ofp.write( " [-D] dry run (do not connect to GRBL)\n")
  48. ofp.write( " [-z <threshold>] z threshold (default to " + str(z_threshold) + ")\n")
  49. ofp.write( " [-p <zplunge>] amount under height sensed part to plunge (default " + str(z_plunge_mm) + "mm)\n")
  50. ofp.write( " [-T <device>] use <device>\n")
  51. ofp.write( " [-h|--help] help (this screen)\n")
  52. ofp.write( "\n")
  53. gcode_file = None
  54. height_map_file = None
  55. out_height_map_file = None
  56. try:
  57. opts, args = getopt.getopt(sys.argv[1:], "hm:g:z:Dp:O:T:", ["help", "output="])
  58. except getopt.GetoptError, err:
  59. print str(err)
  60. usage()
  61. sys.exit(2)
  62. for o, a in opts:
  63. if o == "-g":
  64. gcode_file = a
  65. elif o in ("-h", "--help"):
  66. usage(sys.stdout)
  67. sys.exit(0)
  68. elif o == "-m":
  69. height_map_file = a
  70. elif o == "-z":
  71. z_threshold = float(a)
  72. elif o == "-p":
  73. z_plunge_mm = float(a)
  74. elif o == "-D":
  75. dry_run = True
  76. elif o == "-O":
  77. out_height_map_file = a
  78. elif o == "-T":
  79. tty_device = a
  80. else:
  81. assert False, "unhandled option"
  82. if gcode_file is None:
  83. sys.stderr.write("Provide gcode file\n")
  84. usage(sys.stderr)
  85. sys.exit(-1)
  86. pnts = []
  87. pnts_xy = []
  88. pntz_z = []
  89. def read_gcode_file(gcode_filename):
  90. cur_x = 0.0
  91. cur_y = 0.0
  92. xvalid = False
  93. yvalid = False
  94. res = {
  95. "lines":[],
  96. "status":"init",
  97. "error":"",
  98. "min_x":0.0,
  99. "min_y":0.0,
  100. "max_x":0.0,
  101. "max_y":0.0
  102. }
  103. lines = []
  104. with open(gcode_filename, "r") as gc:
  105. for line in gc:
  106. line = line.strip()
  107. line = line.rstrip('\n')
  108. res["lines"].append(line)
  109. m = re.match('^\s*(\(|;)', line)
  110. if m: continue
  111. m = re.match('.*[xX]\s*(-?\d+(\.\d+)?)', line)
  112. if m:
  113. cur_x = float(m.group(1))
  114. if not xvalid:
  115. res["min_x"] = cur_x
  116. res["max_x"] = cur_x
  117. xvalid = True
  118. if cur_x < res["min_x"]: res["min_x"] = cur_x
  119. if cur_x > res["max_x"]: res["max_x"] = cur_x
  120. m = re.match('.*[yY]\s*(-?\d+(\.\d+)?)', line)
  121. if m:
  122. cur_y = float(m.group(1))
  123. if not yvalid:
  124. res["min_y"] = cur_y
  125. res["max_y"] = cur_y
  126. yvalid = True
  127. if cur_y < res["min_y"]: res["min_y"] = cur_y
  128. if cur_y > res["max_y"]: res["max_y"] = cur_y
  129. res["status"] = "ok"
  130. return res
  131. def interpolate_gcode(gcode, pnts_xy, pnts_z, _feed=DEFAULT_FEED_RATE):
  132. unit = "mm"
  133. cur_x, cur_y, cur_z = 0, 0, 0
  134. z_pos = 'up'
  135. z_pos_prv = z_pos
  136. z_threshold = 0.0
  137. z_plunge_inch = -0.006
  138. z_plunge_mm = z_plunge_inch * 25.4
  139. lines = []
  140. z_leeway = 1
  141. z_ub = pnts_z[0]
  142. g1_feed = _feed
  143. for idx in range(len(pnts_z)):
  144. if z_ub < pnts_z[idx]: z_ub = pnts_z[idx]
  145. z_ub += z_leeway
  146. for line in gcode["lines"]:
  147. line = line.strip()
  148. is_move = 0
  149. l = line.rstrip('\n')
  150. # skip comments
  151. # assumes comments encased in parens all on one line
  152. #
  153. m = re.match('^\s*(\(|;)', l)
  154. if m:
  155. lines.append(l)
  156. continue
  157. m = re.match('^\s*[gG]\s*(0*\d*)([^\d]|$)', l)
  158. if m:
  159. tmp_mode = m.group(1)
  160. if re.match('^0*20$', tmp_mode):
  161. unit = "inch"
  162. elif re.match('^0*21$', tmp_mode):
  163. unit = "mm"
  164. m = re.match('^\s*[gG]\s*(0*[01])[^\d](.*)', l)
  165. if m:
  166. g_mode = m.group(1)
  167. l = m.group(2)
  168. m = re.match('.*[xX]\s*(-?\d+(\.\d+)?)', l)
  169. if m:
  170. is_move = 1
  171. cur_x = m.group(1)
  172. m = re.match('.*[yY]\s*(-?\d+(\.\d+)?)', l)
  173. if m:
  174. is_move = 1
  175. cur_y = m.group(1)
  176. m = re.match('.*[zZ]\s*(-?\d+(\.\d+)?)', l)
  177. if m:
  178. is_move = 1
  179. cur_z = m.group(1)
  180. if ( float(cur_z) >= z_threshold ):
  181. z_pos = 'up'
  182. else:
  183. z_pos = 'down'
  184. if is_move and (not g_mode):
  185. return None
  186. if not is_move:
  187. lines.append(l)
  188. continue
  189. if (z_pos == 'up'):
  190. lines.append("G" + str(g_mode) + " Z{0:.8f}".format(z_ub))
  191. elif (z_pos == 'down'):
  192. interpolated_z = griddata(pnts_xy, pnts_z, (cur_x, cur_y), method='linear')
  193. if np.isnan(interpolated_z):
  194. sys.stderr.write("ERROR: NaN at " + str(cur_x) + " " + str(cur_y))
  195. sys.stdout.write("ERROR: NaN at " + str(cur_x) + " " + str(cur_y))
  196. sys.stderr.flush()
  197. sys.stdout.flush()
  198. sys.exit(-5)
  199. if unit == "inch":
  200. z_plunge = z_plunge_inch
  201. elif unit == "mm":
  202. z_plunge = z_plunge_mm
  203. else:
  204. #print "ERROR: unit improperly set"
  205. return None
  206. interpolated_z += z_plunge
  207. x_f = float(cur_x)
  208. y_f = float(cur_y)
  209. if z_pos_prv == "up":
  210. lines.append("G0 X{0:.8f}".format(x_f) + " Y{0:.8f}".format(y_f) + " Z{0:.8f}".format(z_ub))
  211. #print "G" + g_mode, "X{0:.8f}".format(x_f), "Y{0:.8f}".format(y_f), "Z{0:.8f}".format(interpolated_z)
  212. #lines.append("G" + str(g_mode) + " X{0:.8f}".format(x_f) + " Y{0:.8f}".format(y_f) + " Z{0:.8f}".format(interpolated_z))
  213. 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))
  214. z_pos_prv = z_pos
  215. return lines
  216. _gc = read_gcode_file(gcode_file)
  217. pnts = []
  218. if not dry_run:
  219. grbl.setup(tty_device)
  220. grbl.send_initial_command("")
  221. sys.stdout.write("\n#### ")
  222. cprint("READY TO CUT, REMOVE PROBE AND PRESS ENTER TO CONTINUE", "red", attrs=['blink'])
  223. sys.stdout.flush()
  224. sys.stdin.readline()
  225. if not dry_run:
  226. grbl.send_command("G1Z5")
  227. grbl.send_command("M42")
  228. for line in _gc["lines"]:
  229. line = line.strip()
  230. if len(line)==0: continue
  231. if line[0] == '#': continue
  232. if line[0] == '(': continue
  233. print("## sending:", line)
  234. sys.stdout.flush()
  235. r = grbl.send_command(line)
  236. print "### got:", r
  237. sys.stdout.flush()
  238. grbl.send_command("M43")