TclCommandDrillcncjob.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. from tclCommands.TclCommand import TclCommandSignaled
  2. from FlatCAMObj import FlatCAMExcellon
  3. import collections
  4. import math
  5. import gettext
  6. import FlatCAMTranslation as fcTranslate
  7. import builtins
  8. fcTranslate.apply_language('strings')
  9. if '_' not in builtins.__dict__:
  10. _ = gettext.gettext
  11. class TclCommandDrillcncjob(TclCommandSignaled):
  12. """
  13. Tcl shell command to Generates a Drill CNC Job from a Excellon Object.
  14. """
  15. # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
  16. aliases = ['drillcncjob']
  17. description = '%s %s' % ("--", "Generates a Drill CNC Job object from a Excellon Object.")
  18. # dictionary of types from Tcl command, needs to be ordered
  19. arg_names = collections.OrderedDict([
  20. ('name', str)
  21. ])
  22. # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
  23. option_types = collections.OrderedDict([
  24. ('drilled_dias', str),
  25. ('drillz', float),
  26. ('dpp', float),
  27. ('travelz', float),
  28. ('feedrate_z', float),
  29. ('feedrate_rapid', float),
  30. ('spindlespeed', int),
  31. ('toolchangez', float),
  32. ('toolchangexy', tuple),
  33. ('startz', float),
  34. ('endz', float),
  35. ('endxy', tuple),
  36. ('dwelltime', float),
  37. ('pp', str),
  38. ('opt_type', str),
  39. ('diatol', float),
  40. ('muted', str),
  41. ('outname', str)
  42. ])
  43. # array of mandatory options for current Tcl command: required = {'name','outname'}
  44. required = ['name']
  45. # structured help for current command, args needs to be ordered
  46. help = {
  47. 'main': "Generates a Drill CNC Job from a Excellon Object.",
  48. 'args': collections.OrderedDict([
  49. ('name', 'Name of the source object.'),
  50. ('drilled_dias',
  51. 'Comma separated tool diameters of the drills to be drilled (example: 0.6,1.0 or 3.125). '
  52. 'No space allowed'),
  53. ('drillz', 'Drill depth into material (example: -2.0). Negative value.'),
  54. ('dpp', 'Progressive drilling into material with a specified step (example: 0.7). Positive value.'),
  55. ('travelz', 'Travel distance above material (example: 2.0).'),
  56. ('feedrate_z', 'Drilling feed rate. It is the speed on the Z axis.'),
  57. ('feedrate_rapid', 'Rapid drilling feed rate.'),
  58. ('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'),
  59. ('toolchangez', 'Z distance for toolchange (example: 30.0).\n'
  60. 'If used in the command then a toolchange event will be included in gcode'),
  61. ('toolchangexy', 'X, Y coordonates for toolchange in format (x, y) (example: (2.0, 3.1) ).'),
  62. ('startz', 'The Z coordinate at job start (example: 30.0).'),
  63. ('endz', 'The Z coordinate at job end (example: 30.0).'),
  64. ('endxy', 'The X,Y coordinates at job end in format (x, y) (example: (30.0, 15.2)).'),
  65. ('dwelltime', 'Time to pause to allow the spindle to reach the full speed.\n'
  66. 'If it is not used in command then it will not be included'),
  67. ('pp', 'This is the Excellon preprocessor name: case_sensitive, no_quotes'),
  68. ('opt_type', 'Name of move optimization type. B by default for Basic OR-Tools, M for Metaheuristic OR-Tools'
  69. 'T from Travelling Salesman Algorithm. B and M works only for 64bit version of FlatCAM and '
  70. 'T works only for 32bit version of FlatCAM'),
  71. ('diatol', 'Tolerance. Percentange (0.0 ... 100.0) within which dias in drilled_dias will be judged to be '
  72. 'the same as the ones in the tools from the Excellon object. E.g: if in drill_dias we have a '
  73. 'diameter with value 1.0, in the Excellon we have a tool with dia = 1.05 and we set a tolerance '
  74. 'diatol = 5.0 then the drills with the dia = (0.95 ... 1.05) '
  75. 'in Excellon will be processed. Float number.'),
  76. ('muted', 'It will not put errors in the Shell or status bar. Can be True (1) or False (0).'),
  77. ('outname', 'Name of the resulting Geometry object.')
  78. ]),
  79. 'examples': ['drillcncjob test.TXT -drillz -1.5 -travelz 14 -feedrate_z 222 -feedrate_rapid 456 '
  80. '-spindlespeed 777 -toolchangez 33 -endz 22 -pp default\n'
  81. 'Usage of -feedrate_rapid matter only when the preprocessor is using it, like -marlin-.',
  82. 'drillcncjob test.DRL -drillz -1.7 -dpp 0.5 -travelz 2 -feedrate_z 800 -endxy 3,3']
  83. }
  84. def execute(self, args, unnamed_args):
  85. """
  86. execute current TCL shell command
  87. :param args: array of known named arguments and options
  88. :param unnamed_args: array of other values which were passed into command
  89. without -somename and we do not have them in known arg_names
  90. :return: None or exception
  91. """
  92. name = args['name']
  93. obj = self.app.collection.get_by_name(name)
  94. if 'outname' not in args:
  95. args['outname'] = name + "_cnc"
  96. if 'muted' in args:
  97. try:
  98. par = args['muted'].capitalize()
  99. except AttributeError:
  100. par = args['muted']
  101. muted = bool(eval(par))
  102. else:
  103. muted = False
  104. if obj is None:
  105. if muted is False:
  106. self.raise_tcl_error("Object not found: %s" % name)
  107. else:
  108. return "fail"
  109. if not isinstance(obj, FlatCAMExcellon):
  110. if muted is False:
  111. self.raise_tcl_error('Expected FlatCAMExcellon, got %s %s.' % (name, type(obj)))
  112. else:
  113. return "fail"
  114. xmin = obj.options['xmin']
  115. ymin = obj.options['ymin']
  116. xmax = obj.options['xmax']
  117. ymax = obj.options['ymax']
  118. def job_init(job_obj, app_obj):
  119. # tools = args["tools"] if "tools" in args else 'all'
  120. try:
  121. if 'drilled_dias' in args and args['drilled_dias'] != 'all':
  122. diameters = [x.strip() for x in args['drilled_dias'].split(",") if x != '']
  123. nr_diameters = len(diameters)
  124. req_tools = set()
  125. for tool in obj.tools:
  126. for req_dia in diameters:
  127. obj_dia_form = float('%.*f' % (obj.decimals, float(obj.tools[tool]["C"])))
  128. req_dia_form = float('%.*f' % (obj.decimals, float(req_dia)))
  129. if 'diatol' in args:
  130. tolerance = args['diatol'] / 100
  131. tolerance = 0.0 if tolerance < 0.0 else tolerance
  132. tolerance = 1.0 if tolerance > 1.0 else tolerance
  133. if math.isclose(obj_dia_form, req_dia_form, rel_tol=tolerance):
  134. req_tools.add(tool)
  135. nr_diameters -= 1
  136. else:
  137. if obj_dia_form == req_dia_form:
  138. req_tools.add(tool)
  139. nr_diameters -= 1
  140. if nr_diameters > 0:
  141. if muted is False:
  142. self.raise_tcl_error("One or more tool diameters of the drills to be drilled passed to the "
  143. "TclCommand are not actual tool diameters in the Excellon object.")
  144. else:
  145. return "fail"
  146. # make a string of diameters separated by comma; this is what generate_from_excellon_by_tool() is
  147. # expecting as tools parameter
  148. tools = ','.join(req_tools)
  149. # no longer needed
  150. del args['drilled_dias']
  151. del args['diatol']
  152. # Split and put back. We are passing the whole dictionary later.
  153. # args['milled_dias'] = [x.strip() for x in args['tools'].split(",")]
  154. else:
  155. tools = 'all'
  156. except Exception as e:
  157. tools = 'all'
  158. if muted is False:
  159. self.raise_tcl_error("Bad tools: %s" % str(e))
  160. else:
  161. return "fail"
  162. used_tools_info = []
  163. used_tools_info.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")])
  164. # populate the information's list for used tools
  165. if tools == 'all':
  166. sort = []
  167. for k, v in list(obj.tools.items()):
  168. sort.append((k, v.get('C')))
  169. sorted_tools = sorted(sort, key=lambda t1: t1[1])
  170. use_tools = [i[0] for i in sorted_tools]
  171. for tool_no in use_tools:
  172. tool_dia_used = obj.tools[tool_no]['C']
  173. drill_cnt = 0 # variable to store the nr of drills per tool
  174. slot_cnt = 0 # variable to store the nr of slots per tool
  175. # Find no of drills for the current tool
  176. for drill in obj.drills:
  177. if drill['tool'] == tool_no:
  178. drill_cnt += 1
  179. # Find no of slots for the current tool
  180. for slot in obj.slots:
  181. if slot['tool'] == tool_no:
  182. slot_cnt += 1
  183. used_tools_info.append([str(tool_no), str(tool_dia_used), str(drill_cnt), str(slot_cnt)])
  184. drillz = args["drillz"] if "drillz" in args and args["drillz"] is not None else obj.options["cutz"]
  185. if "toolchangez" in args:
  186. toolchange = True
  187. if args["toolchangez"] is not None:
  188. toolchangez = args["toolchangez"]
  189. else:
  190. toolchangez = obj.options["toolchangez"]
  191. else:
  192. toolchange = False
  193. toolchangez = 0.0
  194. xy_toolchange = args["toolchangexy"] if "toolchangexy" in args and args["toolchangexy"] else \
  195. obj.options["toolchangexy"]
  196. xy_toolchange = ','.join([xy_toolchange[0], xy_toolchange[2]])
  197. endz = args["endz"] if "endz" in args and args["endz"] is not None else obj.options["endz"]
  198. xy_end = args["endxy"] if "endxy" in args and args["endxy"] else '0,0'
  199. xy_end = ','.join([xy_end[0], xy_end[2]])
  200. print(xy_end)
  201. opt_type = args["opt_type"] if "opt_type" in args and args["opt_type"] else 'B'
  202. # ##########################################################################################
  203. # ################# Set parameters #########################################################
  204. # ##########################################################################################
  205. job_obj.origin_kind = 'excellon'
  206. job_obj.options['Tools_in_use'] = used_tools_info
  207. job_obj.options['type'] = 'Excellon'
  208. pp_excellon_name = args["pp"] if "pp" in args and args["pp"] else obj.options["ppname_e"]
  209. job_obj.pp_excellon_name = pp_excellon_name
  210. job_obj.options['ppname_e'] = pp_excellon_name
  211. if 'dpp' in args:
  212. job_obj.multidepth = True
  213. if args['dpp'] is not None:
  214. job_obj.z_depthpercut = float(args['dpp'])
  215. else:
  216. job_obj.z_depthpercut = float(obj.options["dpp"])
  217. else:
  218. job_obj.multidepth = False
  219. job_obj.z_depthpercut = 0.0
  220. job_obj.z_move = float(args["travelz"]) if "travelz" in args and args["travelz"] else obj.options["travelz"]
  221. job_obj.feedrate = float(args["feedrate_z"]) if "feedrate_z" in args and args["feedrate_z"] else \
  222. obj.options["feedrate_z"]
  223. job_obj.z_feedrate = float(args["feedrate_z"]) if "feedrate_z" in args and args["feedrate_z"] else \
  224. obj.options["feedrate_z"]
  225. job_obj.feedrate_rapid = float(args["feedrate_rapid"]) \
  226. if "feedrate_rapid" in args and args["feedrate_rapid"] else obj.options["feedrate_rapid"]
  227. job_obj.spindlespeed = float(args["spindlespeed"]) if "spindlespeed" in args else None
  228. job_obj.spindledir = self.app.defaults['excellon_spindledir']
  229. if 'dwelltime' in args:
  230. job_obj.dwell = True
  231. if args['dwelltime'] is not None:
  232. job_obj.dwelltime = float(args['dwelltime'])
  233. else:
  234. job_obj.dwelltime = float(obj.options["dwelltime"])
  235. else:
  236. job_obj.dwell = False
  237. job_obj.dwelltime = 0.0
  238. job_obj.toolchange_xy_type = "excellon"
  239. job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
  240. job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"])
  241. job_obj.options['xmin'] = xmin
  242. job_obj.options['ymin'] = ymin
  243. job_obj.options['xmax'] = xmax
  244. job_obj.options['ymax'] = ymax
  245. job_obj.z_cut = float(drillz)
  246. job_obj.toolchange = toolchange
  247. job_obj.xy_toolchange = xy_toolchange
  248. job_obj.z_toolchange = float(toolchangez)
  249. job_obj.startz = float(args["startz"]) if "endz" in args and args["endz"] is not None else (0, 0)
  250. job_obj.endz = float(endz)
  251. job_obj.xy_end = xy_end
  252. job_obj.excellon_optimization_type = opt_type
  253. ret_val = job_obj.generate_from_excellon_by_tool(obj, tools, use_ui=False)
  254. if ret_val == 'fail':
  255. return 'fail'
  256. for t_item in job_obj.exc_cnc_tools:
  257. job_obj.exc_cnc_tools[t_item]['data']['offset'] = \
  258. float(job_obj.exc_cnc_tools[t_item]['offset_z']) + float(drillz)
  259. job_obj.exc_cnc_tools[t_item]['data']['ppname_e'] = obj.options['ppname_e']
  260. job_obj.gcode_parse()
  261. job_obj.create_geometry()
  262. self.app.new_object("cncjob", args['outname'], job_init, plot=False)