ToolSolderPaste.py 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231
  1. from FlatCAMTool import FlatCAMTool
  2. from copy import copy,deepcopy
  3. from ObjectCollection import *
  4. from FlatCAMApp import *
  5. from PyQt5 import QtGui, QtCore, QtWidgets
  6. from GUIElements import IntEntry, RadioSet, LengthEntry
  7. from FlatCAMCommon import LoudDict
  8. from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
  9. class ToolSolderPaste(FlatCAMTool):
  10. toolName = "Solder Paste Tool"
  11. def __init__(self, app):
  12. FlatCAMTool.__init__(self, app)
  13. ## Title
  14. title_label = QtWidgets.QLabel("%s" % self.toolName)
  15. title_label.setStyleSheet("""
  16. QLabel
  17. {
  18. font-size: 16px;
  19. font-weight: bold;
  20. }
  21. """)
  22. self.layout.addWidget(title_label)
  23. ## Form Layout
  24. obj_form_layout = QtWidgets.QFormLayout()
  25. self.layout.addLayout(obj_form_layout)
  26. ## Gerber Object to be used for solderpaste dispensing
  27. self.obj_combo = QtWidgets.QComboBox()
  28. self.obj_combo.setModel(self.app.collection)
  29. self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  30. self.obj_combo.setCurrentIndex(1)
  31. self.object_label = QtWidgets.QLabel("Gerber: ")
  32. self.object_label.setToolTip(
  33. "Gerber Solder paste object. "
  34. )
  35. obj_form_layout.addRow(self.object_label, self.obj_combo)
  36. #### Tools ####
  37. self.tools_table_label = QtWidgets.QLabel('<b>Tools Table</b>')
  38. self.tools_table_label.setToolTip(
  39. "Tools pool from which the algorithm\n"
  40. "will pick the ones used for dispensing solder paste."
  41. )
  42. self.layout.addWidget(self.tools_table_label)
  43. self.tools_table = FCTable()
  44. self.layout.addWidget(self.tools_table)
  45. self.tools_table.setColumnCount(3)
  46. self.tools_table.setHorizontalHeaderLabels(['#', 'Diameter', ''])
  47. self.tools_table.setColumnHidden(2, True)
  48. self.tools_table.setSortingEnabled(False)
  49. # self.tools_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
  50. self.tools_table.horizontalHeaderItem(0).setToolTip(
  51. "This is the Tool Number.\n"
  52. "The solder dispensing will start with the tool with the biggest \n"
  53. "diameter, continuing until there are no more Nozzle tools.\n"
  54. "If there are no longer tools but there are still pads not covered\n "
  55. "with solder paste, the app will issue a warning message box."
  56. )
  57. self.tools_table.horizontalHeaderItem(1).setToolTip(
  58. "Nozzle tool Diameter. It's value (in current FlatCAM units)\n"
  59. "is the width of the solder paste dispensed.")
  60. self.empty_label = QtWidgets.QLabel('')
  61. self.layout.addWidget(self.empty_label)
  62. #### Add a new Tool ####
  63. hlay_tools = QtWidgets.QHBoxLayout()
  64. self.layout.addLayout(hlay_tools)
  65. self.addtool_entry_lbl = QtWidgets.QLabel('<b>New Nozzle Tool:</b>')
  66. self.addtool_entry_lbl.setToolTip(
  67. "Diameter for the new Nozzle tool to add in the Tool Table"
  68. )
  69. self.addtool_entry = FCEntry()
  70. # hlay.addWidget(self.addtool_label)
  71. # hlay.addStretch()
  72. hlay_tools.addWidget(self.addtool_entry_lbl)
  73. hlay_tools.addWidget(self.addtool_entry)
  74. grid0 = QtWidgets.QGridLayout()
  75. self.layout.addLayout(grid0)
  76. self.addtool_btn = QtWidgets.QPushButton('Add')
  77. self.addtool_btn.setToolTip(
  78. "Add a new nozzle tool to the Tool Table\n"
  79. "with the diameter specified above."
  80. )
  81. self.deltool_btn = QtWidgets.QPushButton('Delete')
  82. self.deltool_btn.setToolTip(
  83. "Delete a selection of tools in the Tool Table\n"
  84. "by first selecting a row(s) in the Tool Table."
  85. )
  86. self.soldergeo_btn = QtWidgets.QPushButton("Generate Geo")
  87. self.soldergeo_btn.setToolTip(
  88. "Generate solder paste dispensing geometry."
  89. )
  90. step1_lbl = QtWidgets.QLabel("<b>STEP 1:</b>")
  91. step1_lbl.setToolTip(
  92. "First step is to select a number of nozzle tools for usage\n"
  93. "and then create a solder paste dispensing geometry out of an\n"
  94. "Solder Paste Mask Gerber file."
  95. )
  96. grid0.addWidget(self.addtool_btn, 0, 0)
  97. # grid2.addWidget(self.copytool_btn, 0, 1)
  98. grid0.addWidget(self.deltool_btn, 0, 2)
  99. grid0.addWidget(step1_lbl, 2, 0)
  100. grid0.addWidget(self.soldergeo_btn, 2, 2)
  101. ## Form Layout
  102. geo_form_layout = QtWidgets.QFormLayout()
  103. self.layout.addLayout(geo_form_layout)
  104. ## Geometry Object to be used for solderpaste dispensing
  105. self.geo_obj_combo = QtWidgets.QComboBox()
  106. self.geo_obj_combo.setModel(self.app.collection)
  107. self.geo_obj_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
  108. self.geo_obj_combo.setCurrentIndex(1)
  109. self.geo_object_label = QtWidgets.QLabel("Geometry:")
  110. self.geo_object_label.setToolTip(
  111. "Geometry Solder paste object.\n"
  112. "In order to enable the GCode generation section,\n"
  113. "the name of the object has to end in:\n"
  114. "'_solderpaste' as a protection."
  115. )
  116. geo_form_layout.addRow(self.geo_object_label, self.geo_obj_combo)
  117. self.gcode_frame = QtWidgets.QFrame()
  118. self.gcode_frame.setContentsMargins(0, 0, 0, 0)
  119. self.layout.addWidget(self.gcode_frame)
  120. self.gcode_box = QtWidgets.QVBoxLayout()
  121. self.gcode_box.setContentsMargins(0, 0, 0, 0)
  122. self.gcode_frame.setLayout(self.gcode_box)
  123. ## Form Layout
  124. self.gcode_form_layout = QtWidgets.QFormLayout()
  125. self.gcode_box.addLayout(self.gcode_form_layout)
  126. # Z dispense start
  127. self.z_start_entry = FCEntry()
  128. self.z_start_label = QtWidgets.QLabel("Z Dispense Start:")
  129. self.z_start_label.setToolTip(
  130. "The height (Z) when solder paste dispensing starts."
  131. )
  132. self.gcode_form_layout.addRow(self.z_start_label, self.z_start_entry)
  133. # Z dispense
  134. self.z_dispense_entry = FCEntry()
  135. self.z_dispense_label = QtWidgets.QLabel("Z Dispense:")
  136. self.z_dispense_label.setToolTip(
  137. "The height (Z) when doing solder paste dispensing."
  138. )
  139. self.gcode_form_layout.addRow(self.z_dispense_label, self.z_dispense_entry)
  140. # Z dispense stop
  141. self.z_stop_entry = FCEntry()
  142. self.z_stop_label = QtWidgets.QLabel("Z Dispense Stop:")
  143. self.z_stop_label.setToolTip(
  144. "The height (Z) when solder paste dispensing stops."
  145. )
  146. self.gcode_form_layout.addRow(self.z_stop_label, self.z_stop_entry)
  147. # Z travel
  148. self.z_travel_entry = FCEntry()
  149. self.z_travel_label = QtWidgets.QLabel("Z Travel:")
  150. self.z_travel_label.setToolTip(
  151. "The height (Z) for travel between pads\n"
  152. "(without dispensing solder paste)."
  153. )
  154. self.gcode_form_layout.addRow(self.z_travel_label, self.z_travel_entry)
  155. # Feedrate X-Y
  156. self.frxy_entry = FCEntry()
  157. self.frxy_label = QtWidgets.QLabel("Feedrate X-Y:")
  158. self.frxy_label.setToolTip(
  159. "Feedrate (speed) while moving on the X-Y plane."
  160. )
  161. self.gcode_form_layout.addRow(self.frxy_label, self.frxy_entry)
  162. # Feedrate Z
  163. self.frz_entry = FCEntry()
  164. self.frz_label = QtWidgets.QLabel("Feedrate Z:")
  165. self.frz_label.setToolTip(
  166. "Feedrate (speed) while moving vertically\n"
  167. "(on Z plane)."
  168. )
  169. self.gcode_form_layout.addRow(self.frz_label, self.frz_entry)
  170. # Spindle Speed Forward
  171. self.speedfwd_entry = FCEntry()
  172. self.speedfwd_label = QtWidgets.QLabel("Spindle Speed FWD:")
  173. self.speedfwd_label.setToolTip(
  174. "The dispenser speed while pushing solder paste\n"
  175. "through the dispenser nozzle."
  176. )
  177. self.gcode_form_layout.addRow(self.speedfwd_label, self.speedfwd_entry)
  178. # Dwell Forward
  179. self.dwellfwd_entry = FCEntry()
  180. self.dwellfwd_label = QtWidgets.QLabel("Dwell FWD:")
  181. self.dwellfwd_label.setToolTip(
  182. "Pause after solder dispensing."
  183. )
  184. self.gcode_form_layout.addRow(self.dwellfwd_label, self.dwellfwd_entry)
  185. # Spindle Speed Reverse
  186. self.speedrev_entry = FCEntry()
  187. self.speedrev_label = QtWidgets.QLabel("Spindle Speed REV:")
  188. self.speedrev_label.setToolTip(
  189. "The dispenser speed while retracting solder paste\n"
  190. "through the dispenser nozzle."
  191. )
  192. self.gcode_form_layout.addRow(self.speedrev_label, self.speedrev_entry)
  193. # Dwell Reverse
  194. self.dwellrev_entry = FCEntry()
  195. self.dwellrev_label = QtWidgets.QLabel("Dwell REV:")
  196. self.dwellrev_label.setToolTip(
  197. "Pause after solder paste dispenser retracted,\n"
  198. "to allow pressure equilibrium."
  199. )
  200. self.gcode_form_layout.addRow(self.dwellrev_label, self.dwellrev_entry)
  201. # Postprocessors
  202. pp_label = QtWidgets.QLabel('PostProcessors:')
  203. pp_label.setToolTip(
  204. "Files that control the GCode generation."
  205. )
  206. self.pp_combo = FCComboBox()
  207. self.pp_combo.setStyleSheet('background-color: rgb(255,255,255)')
  208. self.gcode_form_layout.addRow(pp_label, self.pp_combo)
  209. ## Buttons
  210. grid1 = QtWidgets.QGridLayout()
  211. self.gcode_box.addLayout(grid1)
  212. self.solder_gcode_btn = QtWidgets.QPushButton("Generate GCode")
  213. self.solder_gcode_btn.setToolTip(
  214. "Generate GCode for Solder Paste dispensing\n"
  215. "on PCB pads."
  216. )
  217. step2_lbl = QtWidgets.QLabel("<b>STEP 2:</b>")
  218. step2_lbl.setToolTip(
  219. "Second step is to select a solder paste dispensing geometry,\n"
  220. "set the CAM parameters and then generate a CNCJob object which\n"
  221. "will pe painted on canvas in blue color."
  222. )
  223. grid1.addWidget(step2_lbl, 0, 0)
  224. grid1.addWidget(self.solder_gcode_btn, 0, 2)
  225. ## Form Layout
  226. cnc_form_layout = QtWidgets.QFormLayout()
  227. self.gcode_box.addLayout(cnc_form_layout)
  228. ## Gerber Object to be used for solderpaste dispensing
  229. self.cnc_obj_combo = QtWidgets.QComboBox()
  230. self.cnc_obj_combo.setModel(self.app.collection)
  231. self.cnc_obj_combo.setRootModelIndex(self.app.collection.index(3, 0, QtCore.QModelIndex()))
  232. self.cnc_obj_combo.setCurrentIndex(1)
  233. self.cnc_object_label = QtWidgets.QLabel("CNCJob: ")
  234. self.cnc_object_label.setToolTip(
  235. "CNCJob Solder paste object.\n"
  236. "In order to enable the GCode save section,\n"
  237. "the name of the object has to end in:\n"
  238. "'_solderpaste' as a protection."
  239. )
  240. cnc_form_layout.addRow(self.cnc_object_label, self.cnc_obj_combo)
  241. self.save_gcode_frame = QtWidgets.QFrame()
  242. self.save_gcode_frame.setContentsMargins(0, 0, 0, 0)
  243. self.layout.addWidget(self.save_gcode_frame)
  244. self.save_gcode_box = QtWidgets.QVBoxLayout()
  245. self.save_gcode_box.setContentsMargins(0, 0, 0, 0)
  246. self.save_gcode_frame.setLayout(self.save_gcode_box)
  247. ## Buttons
  248. grid2 = QtWidgets.QGridLayout()
  249. self.save_gcode_box.addLayout(grid2)
  250. self.solder_gcode_view_btn = QtWidgets.QPushButton("View GCode")
  251. self.solder_gcode_view_btn.setToolTip(
  252. "View the generated GCode for Solder Paste dispensing\n"
  253. "on PCB pads."
  254. )
  255. self.solder_gcode_save_btn = QtWidgets.QPushButton("Save GCode")
  256. self.solder_gcode_save_btn.setToolTip(
  257. "Save the generated GCode for Solder Paste dispensing\n"
  258. "on PCB pads, to a file."
  259. )
  260. step3_lbl = QtWidgets.QLabel("<b>STEP 3:</b>")
  261. step3_lbl.setToolTip(
  262. "Third step (and last) is to select a CNCJob made from \n"
  263. "a solder paste dispensing geometry, and then view/save it's GCode."
  264. )
  265. grid2.addWidget(step3_lbl, 0, 0)
  266. grid2.addWidget(self.solder_gcode_view_btn, 0, 2)
  267. grid2.addWidget(self.solder_gcode_save_btn, 1, 2)
  268. self.layout.addStretch()
  269. # self.gcode_frame.setDisabled(True)
  270. # self.save_gcode_frame.setDisabled(True)
  271. self.tools = {}
  272. self.tooluid = 0
  273. self.options = LoudDict()
  274. self.form_fields = {}
  275. ## Signals
  276. self.addtool_btn.clicked.connect(self.on_tool_add)
  277. self.deltool_btn.clicked.connect(self.on_tool_delete)
  278. self.soldergeo_btn.clicked.connect(self.on_create_geo)
  279. self.solder_gcode_btn.clicked.connect(self.on_create_gcode)
  280. self.solder_gcode_view_btn.clicked.connect(self.on_view_gcode)
  281. self.solder_gcode_save_btn.clicked.connect(self.on_save_gcode)
  282. self.geo_obj_combo.currentIndexChanged.connect(self.on_geo_select)
  283. self.cnc_obj_combo.currentIndexChanged.connect(self.on_cncjob_select)
  284. def run(self):
  285. self.app.report_usage("ToolSolderPaste()")
  286. FlatCAMTool.run(self)
  287. self.set_tool_ui()
  288. self.build_ui()
  289. # if the splitter us hidden, display it
  290. if self.app.ui.splitter.sizes()[0] == 0:
  291. self.app.ui.splitter.setSizes([1, 1])
  292. self.app.ui.notebook.setTabText(2, "SolderPaste Tool")
  293. def install(self, icon=None, separator=None, **kwargs):
  294. FlatCAMTool.install(self, icon, separator, shortcut='ALT+K', **kwargs)
  295. def set_tool_ui(self):
  296. self.form_fields.update({
  297. "tools_solderpaste_new": self.addtool_entry,
  298. "tools_solderpaste_z_start": self.z_start_entry,
  299. "tools_solderpaste_z_dispense": self.z_dispense_entry,
  300. "tools_solderpaste_z_stop": self.z_stop_entry,
  301. "tools_solderpaste_z_travel": self.z_travel_entry,
  302. "tools_solderpaste_frxy": self.frxy_entry,
  303. "tools_solderpaste_frz": self.frz_entry,
  304. "tools_solderpaste_speedfwd": self.speedfwd_entry,
  305. "tools_solderpaste_dwellfwd": self.dwellfwd_entry,
  306. "tools_solderpaste_speedrev": self.speedrev_entry,
  307. "tools_solderpaste_dwellrev": self.dwellrev_entry,
  308. "tools_solderpaste_pp": self.pp_combo
  309. })
  310. self.set_form_from_defaults()
  311. self.read_form_to_options()
  312. self.tools_table.setupContextMenu()
  313. self.tools_table.addContextMenu(
  314. "Add", lambda: self.on_tool_add(dia=None, muted=None), icon=QtGui.QIcon("share/plus16.png"))
  315. self.tools_table.addContextMenu(
  316. "Delete", lambda:
  317. self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
  318. try:
  319. dias = [float(eval(dia)) for dia in self.app.defaults["tools_solderpaste_tools"].split(",")]
  320. except:
  321. log.error("At least one Nozzle tool diameter needed. "
  322. "Verify in Edit -> Preferences -> TOOLS -> Solder Paste Tools.")
  323. return
  324. self.tooluid = 0
  325. self.tools.clear()
  326. for tool_dia in dias:
  327. self.tooluid += 1
  328. self.tools.update({
  329. int(self.tooluid): {
  330. 'tooldia': float('%.4f' % tool_dia),
  331. 'data': deepcopy(self.options),
  332. 'solid_geometry': []
  333. }
  334. })
  335. self.name = ""
  336. self.obj = None
  337. self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
  338. for name in list(self.app.postprocessors.keys()):
  339. # populate only with postprocessor files that start with 'Paste_'
  340. if name.partition('_')[0] != 'Paste':
  341. continue
  342. self.pp_combo.addItem(name)
  343. self.reset_fields()
  344. def build_ui(self):
  345. self.ui_disconnect()
  346. # updated units
  347. self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
  348. sorted_tools = []
  349. for k, v in self.tools.items():
  350. sorted_tools.append(float('%.4f' % float(v['tooldia'])))
  351. sorted_tools.sort(reverse=True)
  352. n = len(sorted_tools)
  353. self.tools_table.setRowCount(n)
  354. tool_id = 0
  355. for tool_sorted in sorted_tools:
  356. for tooluid_key, tooluid_value in self.tools.items():
  357. if float('%.4f' % tooluid_value['tooldia']) == tool_sorted:
  358. tool_id += 1
  359. id = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
  360. id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  361. row_no = tool_id - 1
  362. self.tools_table.setItem(row_no, 0, id) # Tool name/id
  363. # Make sure that the drill diameter when in MM is with no more than 2 decimals
  364. # There are no drill bits in MM with more than 3 decimals diameter
  365. # For INCH the decimals should be no more than 3. There are no drills under 10mils
  366. if self.units == 'MM':
  367. dia = QtWidgets.QTableWidgetItem('%.2f' % tooluid_value['tooldia'])
  368. else:
  369. dia = QtWidgets.QTableWidgetItem('%.3f' % tooluid_value['tooldia'])
  370. dia.setFlags(QtCore.Qt.ItemIsEnabled)
  371. tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
  372. self.tools_table.setItem(row_no, 1, dia) # Diameter
  373. self.tools_table.setItem(row_no, 2, tool_uid_item) # Tool unique ID
  374. # make the diameter column editable
  375. for row in range(tool_id):
  376. self.tools_table.item(row, 1).setFlags(
  377. QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  378. # all the tools are selected by default
  379. self.tools_table.selectColumn(0)
  380. #
  381. self.tools_table.resizeColumnsToContents()
  382. self.tools_table.resizeRowsToContents()
  383. vertical_header = self.tools_table.verticalHeader()
  384. vertical_header.hide()
  385. self.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
  386. horizontal_header = self.tools_table.horizontalHeader()
  387. horizontal_header.setMinimumSectionSize(10)
  388. horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
  389. horizontal_header.resizeSection(0, 20)
  390. horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
  391. # self.tools_table.setSortingEnabled(True)
  392. # sort by tool diameter
  393. # self.tools_table.sortItems(1)
  394. self.tools_table.setMinimumHeight(self.tools_table.getHeight())
  395. self.tools_table.setMaximumHeight(self.tools_table.getHeight())
  396. self.ui_connect()
  397. def update_ui(self, row=None):
  398. self.ui_disconnect()
  399. if row is None:
  400. try:
  401. current_row = self.tools_table.currentRow()
  402. except:
  403. current_row = 0
  404. else:
  405. current_row = row
  406. if current_row < 0:
  407. current_row = 0
  408. # populate the form with the data from the tool associated with the row parameter
  409. try:
  410. tooluid = int(self.tools_table.item(current_row, 2).text())
  411. except Exception as e:
  412. log.debug("Tool missing. Add a tool in Tool Table. %s" % str(e))
  413. return
  414. # update the form
  415. try:
  416. # set the form with data from the newly selected tool
  417. for tooluid_key, tooluid_value in self.tools.items():
  418. if int(tooluid_key) == tooluid:
  419. self.set_form(deepcopy(tooluid_value['data']))
  420. except Exception as e:
  421. log.debug("FlatCAMObj ---> update_ui() " + str(e))
  422. self.ui_connect()
  423. def on_row_selection_change(self):
  424. self.update_ui()
  425. def ui_connect(self):
  426. # on any change to the widgets that matter it will be called self.gui_form_to_storage which will save the
  427. # changes in geometry UI
  428. for i in range(self.gcode_form_layout.count()):
  429. if isinstance(self.gcode_form_layout.itemAt(i).widget(), FCComboBox):
  430. self.gcode_form_layout.itemAt(i).widget().currentIndexChanged.connect(self.read_form_to_tooldata)
  431. if isinstance(self.gcode_form_layout.itemAt(i).widget(), FCEntry):
  432. self.gcode_form_layout.itemAt(i).widget().editingFinished.connect(self.read_form_to_tooldata)
  433. self.tools_table.itemChanged.connect(self.on_tool_edit)
  434. self.tools_table.currentItemChanged.connect(self.on_row_selection_change)
  435. def ui_disconnect(self):
  436. # if connected, disconnect the signal from the slot on item_changed as it creates issues
  437. try:
  438. for i in range(self.gcode_form_layout.count()):
  439. if isinstance(self.gcode_form_layout.itemAt(i).widget(), FCComboBox):
  440. self.gcode_form_layout.itemAt(i).widget().currentIndexChanged.disconnect()
  441. if isinstance(self.gcode_form_layout.itemAt(i).widget(), FCEntry):
  442. self.gcode_form_layout.itemAt(i).widget().editingFinished.disconnect()
  443. except:
  444. pass
  445. try:
  446. self.tools_table.itemChanged.disconnect(self.on_tool_edit)
  447. except:
  448. pass
  449. try:
  450. self.tools_table.currentItemChanged.disconnect(self.on_row_selection_change)
  451. except:
  452. pass
  453. def read_form_to_options(self):
  454. """
  455. Will read all the parameters from Solder Paste Tool UI and update the self.options dictionary
  456. :return:
  457. """
  458. for key in self.form_fields:
  459. self.options[key] = self.form_fields[key].get_value()
  460. def read_form_to_tooldata(self, tooluid=None):
  461. current_row = self.tools_table.currentRow()
  462. uid = tooluid if tooluid else int(self.tools_table.item(current_row, 2).text())
  463. for key in self.form_fields:
  464. self.tools[uid]['data'].update({
  465. key: self.form_fields[key].get_value()
  466. })
  467. def set_form_from_defaults(self):
  468. """
  469. Will read all the parameters of Solder Paste Tool from the app self.defaults and update the UI
  470. :return:
  471. """
  472. for key in self.form_fields:
  473. if key in self.app.defaults:
  474. self.form_fields[key].set_value(self.app.defaults[key])
  475. def set_form(self, val):
  476. """
  477. Will read all the parameters of Solder Paste Tool from the provided val parameter and update the UI
  478. :param val: dictionary with values to store in the form
  479. :param_type: dictionary
  480. :return:
  481. """
  482. if not isinstance(val, dict):
  483. log.debug("ToolSoderPaste.set_form() --> parameter not a dict")
  484. return
  485. for key in self.form_fields:
  486. if key in val:
  487. self.form_fields[key].set_value(val[key])
  488. def on_tool_add(self, dia=None, muted=None):
  489. self.ui_disconnect()
  490. if dia:
  491. tool_dia = dia
  492. else:
  493. try:
  494. tool_dia = float(self.addtool_entry.get_value())
  495. except ValueError:
  496. # try to convert comma to decimal point. if it's still not working error message and return
  497. try:
  498. tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
  499. except ValueError:
  500. self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
  501. "use a number.")
  502. return
  503. if tool_dia is None:
  504. self.build_ui()
  505. self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter to add, in Float format.")
  506. return
  507. if tool_dia == 0:
  508. self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.")
  509. return
  510. # construct a list of all 'tooluid' in the self.tools
  511. tool_uid_list = []
  512. for tooluid_key in self.tools:
  513. tool_uid_item = int(tooluid_key)
  514. tool_uid_list.append(tool_uid_item)
  515. # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
  516. if not tool_uid_list:
  517. max_uid = 0
  518. else:
  519. max_uid = max(tool_uid_list)
  520. self.tooluid = int(max_uid + 1)
  521. tool_dias = []
  522. for k, v in self.tools.items():
  523. for tool_v in v.keys():
  524. if tool_v == 'tooldia':
  525. tool_dias.append(float('%.4f' % v[tool_v]))
  526. if float('%.4f' % tool_dia) in tool_dias:
  527. if muted is None:
  528. self.app.inform.emit("[WARNING_NOTCL]Adding Nozzle tool cancelled. Tool already in Tool Table.")
  529. self.tools_table.itemChanged.connect(self.on_tool_edit)
  530. return
  531. else:
  532. if muted is None:
  533. self.app.inform.emit("[success] New Nozzle tool added to Tool Table.")
  534. self.tools.update({
  535. int(self.tooluid): {
  536. 'tooldia': float('%.4f' % tool_dia),
  537. 'data': deepcopy(self.options),
  538. 'solid_geometry': []
  539. }
  540. })
  541. self.build_ui()
  542. def on_tool_edit(self):
  543. self.ui_disconnect()
  544. tool_dias = []
  545. for k, v in self.tools.items():
  546. for tool_v in v.keys():
  547. if tool_v == 'tooldia':
  548. tool_dias.append(float('%.4f' % v[tool_v]))
  549. for row in range(self.tools_table.rowCount()):
  550. try:
  551. new_tool_dia = float(self.tools_table.item(row, 1).text())
  552. except ValueError:
  553. # try to convert comma to decimal point. if it's still not working error message and return
  554. try:
  555. new_tool_dia = float(self.tools_table.item(row, 1).text().replace(',', '.'))
  556. except ValueError:
  557. self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
  558. "use a number.")
  559. return
  560. tooluid = int(self.tools_table.item(row, 2).text())
  561. # identify the tool that was edited and get it's tooluid
  562. if new_tool_dia not in tool_dias:
  563. self.tools[tooluid]['tooldia'] = new_tool_dia
  564. self.app.inform.emit("[success] Nozzle tool from Tool Table was edited.")
  565. self.build_ui()
  566. return
  567. else:
  568. # identify the old tool_dia and restore the text in tool table
  569. for k, v in self.tools.items():
  570. if k == tooluid:
  571. old_tool_dia = v['tooldia']
  572. break
  573. restore_dia_item = self.tools_table.item(row, 1)
  574. restore_dia_item.setText(str(old_tool_dia))
  575. self.app.inform.emit("[WARNING_NOTCL] Edit cancelled. New diameter value is already in the Tool Table.")
  576. self.build_ui()
  577. def on_tool_delete(self, rows_to_delete=None, all=None):
  578. self.ui_disconnect()
  579. deleted_tools_list = []
  580. if all:
  581. self.tools.clear()
  582. self.build_ui()
  583. return
  584. if rows_to_delete:
  585. try:
  586. for row in rows_to_delete:
  587. tooluid_del = int(self.tools_table.item(row, 2).text())
  588. deleted_tools_list.append(tooluid_del)
  589. except TypeError:
  590. deleted_tools_list.append(rows_to_delete)
  591. for t in deleted_tools_list:
  592. self.tools.pop(t, None)
  593. self.build_ui()
  594. return
  595. try:
  596. if self.tools_table.selectedItems():
  597. for row_sel in self.tools_table.selectedItems():
  598. row = row_sel.row()
  599. if row < 0:
  600. continue
  601. tooluid_del = int(self.tools_table.item(row, 2).text())
  602. deleted_tools_list.append(tooluid_del)
  603. for t in deleted_tools_list:
  604. self.tools.pop(t, None)
  605. except AttributeError:
  606. self.app.inform.emit("[WARNING_NOTCL] Delete failed. Select a Nozzle tool to delete.")
  607. return
  608. except Exception as e:
  609. log.debug(str(e))
  610. self.app.inform.emit("[success] Nozzle tool(s) deleted from Tool Table.")
  611. self.build_ui()
  612. def on_geo_select(self):
  613. # if self.geo_obj_combo.currentText().rpartition('_')[2] == 'solderpaste':
  614. # self.gcode_frame.setDisabled(False)
  615. # else:
  616. # self.gcode_frame.setDisabled(True)
  617. pass
  618. def on_cncjob_select(self):
  619. # if self.cnc_obj_combo.currentText().rpartition('_')[2] == 'solderpaste':
  620. # self.save_gcode_frame.setDisabled(False)
  621. # else:
  622. # self.save_gcode_frame.setDisabled(True)
  623. pass
  624. @staticmethod
  625. def distance(pt1, pt2):
  626. return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
  627. def on_create_geo(self):
  628. proc = self.app.proc_container.new("Creating Solder Paste dispensing geometry.")
  629. name = self.obj_combo.currentText()
  630. if name == '':
  631. self.app.inform.emit("[WARNING_NOTCL] No SolderPaste mask Gerber object loaded.")
  632. return
  633. # update the self.options
  634. self.read_form_to_options()
  635. obj = self.app.collection.get_by_name(name)
  636. if type(obj.solid_geometry) is not list and type(obj.solid_geometry) is not MultiPolygon:
  637. obj.solid_geometry = [obj.solid_geometry]
  638. # Sort tools in descending order
  639. sorted_tools = []
  640. for k, v in self.tools.items():
  641. # make sure that the tools diameter is more than zero and not zero
  642. if float(v['tooldia']) > 0:
  643. sorted_tools.append(float('%.4f' % float(v['tooldia'])))
  644. sorted_tools.sort(reverse=True)
  645. def geo_init(geo_obj, app_obj):
  646. geo_obj.options.update(self.options)
  647. geo_obj.solid_geometry = []
  648. geo_obj.tools = {}
  649. geo_obj.multigeo = True
  650. geo_obj.multitool = True
  651. geo_obj.special_group = 'solder_paste_tool'
  652. def solder_line(p, offset):
  653. xmin, ymin, xmax, ymax = p.bounds
  654. min = [xmin, ymin]
  655. max = [xmax, ymax]
  656. min_r = [xmin, ymax]
  657. max_r = [xmax, ymin]
  658. diagonal_1 = LineString([min, max])
  659. diagonal_2 = LineString([min_r, max_r])
  660. round_diag_1 = round(diagonal_1.intersection(p).length, 2)
  661. round_diag_2 = round(diagonal_2.intersection(p).length, 2)
  662. if round_diag_1 == round_diag_2:
  663. l = distance((xmin, ymin), (xmax, ymin))
  664. h = distance((xmin, ymin), (xmin, ymax))
  665. if offset >= l /2 or offset >= h / 2:
  666. return "fail"
  667. if l > h:
  668. h_half = h / 2
  669. start = [xmin, (ymin + h_half)]
  670. stop = [(xmin + l), (ymin + h_half)]
  671. else:
  672. l_half = l / 2
  673. start = [(xmin + l_half), ymin]
  674. stop = [(xmin + l_half), (ymin + h)]
  675. geo = LineString([start, stop])
  676. elif round_diag_1 > round_diag_2:
  677. geo = diagonal_1.intersection(p)
  678. else:
  679. geo = diagonal_2.intersection(p)
  680. offseted_poly = p.buffer(-offset)
  681. geo = geo.intersection(offseted_poly)
  682. return geo
  683. work_geo = obj.solid_geometry
  684. rest_geo = []
  685. tooluid = 1
  686. if not sorted_tools:
  687. self.app.inform.emit("[WARNING_NOTCL] No Nozzle tools in the tool table.")
  688. return 'fail'
  689. for tool in sorted_tools:
  690. offset = tool / 2
  691. for uid, v in self.tools.items():
  692. if float('%.4f' % float(v['tooldia'])) == tool:
  693. tooluid = int(uid)
  694. break
  695. for g in work_geo:
  696. if type(g) == MultiPolygon:
  697. for poly in g:
  698. geom = solder_line(poly, offset=offset)
  699. if geom != 'fail':
  700. try:
  701. geo_obj.tools[tooluid]['solid_geometry'].append(geom)
  702. except KeyError:
  703. geo_obj.tools[tooluid] = {}
  704. geo_obj.tools[tooluid]['solid_geometry'] = []
  705. geo_obj.tools[tooluid]['solid_geometry'].append(geom)
  706. geo_obj.tools[tooluid]['tooldia'] = tool
  707. geo_obj.tools[tooluid]['offset'] = 'Path'
  708. geo_obj.tools[tooluid]['offset_value'] = 0.0
  709. geo_obj.tools[tooluid]['type'] = ' '
  710. geo_obj.tools[tooluid]['tool_type'] = ' '
  711. geo_obj.tools[tooluid]['data'] = {}
  712. else:
  713. rest_geo.append(poly)
  714. elif type(g) == Polygon:
  715. geom = solder_line(g, offset=offset)
  716. if geom != 'fail':
  717. try:
  718. geo_obj.tools[tooluid]['solid_geometry'].append(geom)
  719. except KeyError:
  720. geo_obj.tools[tooluid] = {}
  721. geo_obj.tools[tooluid]['solid_geometry'] = []
  722. geo_obj.tools[tooluid]['solid_geometry'].append(geom)
  723. geo_obj.tools[tooluid]['tooldia'] = tool
  724. geo_obj.tools[tooluid]['offset'] = 'Path'
  725. geo_obj.tools[tooluid]['offset_value'] = 0.0
  726. geo_obj.tools[tooluid]['type'] = ' '
  727. geo_obj.tools[tooluid]['tool_type'] = ' '
  728. geo_obj.tools[tooluid]['data'] = {}
  729. else:
  730. rest_geo.append(g)
  731. work_geo = deepcopy(rest_geo)
  732. rest_geo[:] = []
  733. if not work_geo:
  734. app_obj.inform.emit("[success] Solder Paste geometry generated successfully...")
  735. return
  736. # if we still have geometry not processed at the end of the tools then we failed
  737. # some or all the pads are not covered with solder paste
  738. if rest_geo:
  739. app_obj.inform.emit("[WARNING_NOTCL] Some or all pads have no solder "
  740. "due of inadequate nozzle diameters...")
  741. return 'fail'
  742. def job_thread(app_obj):
  743. try:
  744. app_obj.new_object("geometry", name + "_solderpaste", geo_init)
  745. except Exception as e:
  746. proc.done()
  747. traceback.print_stack()
  748. return
  749. proc.done()
  750. self.app.inform.emit("Generating Solder Paste dispensing geometry...")
  751. # Promise object with the new name
  752. self.app.collection.promise(name)
  753. # Background
  754. self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
  755. # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
  756. def on_view_gcode(self):
  757. # add the tab if it was closed
  758. self.app.ui.plot_tab_area.addTab(self.app.ui.cncjob_tab, "Code Editor")
  759. # Switch plot_area to CNCJob tab
  760. self.app.ui.plot_tab_area.setCurrentWidget(self.app.ui.cncjob_tab)
  761. name = self.cnc_obj_combo.currentText()
  762. obj = self.app.collection.get_by_name(name)
  763. # then append the text from GCode to the text editor
  764. try:
  765. file = StringIO(obj.gcode)
  766. except:
  767. self.app.inform.emit("[ERROR_NOTCL] No Gcode in the object...")
  768. return
  769. try:
  770. for line in file:
  771. proc_line = str(line).strip('\n')
  772. self.app.ui.code_editor.append(proc_line)
  773. except Exception as e:
  774. log.debug('ToolSolderPaste.on_view_gcode() -->%s' % str(e))
  775. self.app.inform.emit('[ERROR]ToolSolderPaste.on_view_gcode() -->%s' % str(e))
  776. return
  777. self.app.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
  778. self.app.handleTextChanged()
  779. self.app.ui.show()
  780. def on_save_gcode(self):
  781. time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
  782. name = self.cnc_obj_combo.currentText()
  783. obj = self.app.collection.get_by_name(name)
  784. _filter_ = "G-Code Files (*.nc);;G-Code Files (*.txt);;G-Code Files (*.tap);;G-Code Files (*.cnc);;" \
  785. "G-Code Files (*.g-code);;All Files (*.*)"
  786. try:
  787. dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name)
  788. filename, _ = QtWidgets.QFileDialog.getSaveFileName(
  789. caption="Export GCode ...",
  790. directory=dir_file_to_save,
  791. filter=_filter_
  792. )
  793. except TypeError:
  794. filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_)
  795. if filename == '':
  796. self.app.inform.emit("[WARNING_NOTCL]Export Machine Code cancelled ...")
  797. return
  798. gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
  799. (str(self.app.version), str(self.app.version_date)) + '\n'
  800. gcode += '(Name: ' + str(name) + ')\n'
  801. gcode += '(Type: ' + "G-code from " + str(obj.options['type']) + " for Solder Paste dispenser" + ')\n'
  802. # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
  803. # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
  804. gcode += '(Units: ' + self.units.upper() + ')\n' + "\n"
  805. gcode += '(Created on ' + time_str + ')\n' + '\n'
  806. gcode += obj.gcode
  807. lines = StringIO(gcode)
  808. ## Write
  809. if filename is not None:
  810. try:
  811. with open(filename, 'w') as f:
  812. for line in lines:
  813. f.write(line)
  814. except FileNotFoundError:
  815. self.app.inform.emit("[WARNING_NOTCL] No such file or directory")
  816. return
  817. self.app.file_saved.emit("gcode", filename)
  818. self.app.inform.emit("[success] Solder paste dispenser GCode file saved to: %s" % filename)
  819. def on_create_gcode(self, use_thread=True):
  820. """
  821. Creates a multi-tool CNCJob out of this Geometry object.
  822. The actual work is done by the target FlatCAMCNCjob object's
  823. `generate_from_geometry_2()` method.
  824. :param z_cut: Cut depth (negative)
  825. :param z_move: Hight of the tool when travelling (not cutting)
  826. :param feedrate: Feed rate while cutting on X - Y plane
  827. :param feedrate_z: Feed rate while cutting on Z plane
  828. :param feedrate_rapid: Feed rate while moving with rapids
  829. :param tooldia: Tool diameter
  830. :param outname: Name of the new object
  831. :param spindlespeed: Spindle speed (RPM)
  832. :param ppname_g Name of the postprocessor
  833. :return: None
  834. """
  835. name = self.obj_combo.currentText()
  836. obj = self.app.collection.get_by_name(name)
  837. if obj.special_group != 'solder_paste_tool':
  838. self.app.inform.emit("[WARNING_NOTCL]This Geometry can't be processed. NOT a solder_paste_tool geometry.")
  839. return
  840. offset_str = ''
  841. multitool_gcode = ''
  842. # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
  843. outname = "%s_%s" % (name, 'cnc_solderpaste')
  844. try:
  845. xmin = obj.options['xmin']
  846. ymin = obj.options['ymin']
  847. xmax = obj.options['xmax']
  848. ymax = obj.options['ymax']
  849. except Exception as e:
  850. log.debug("FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s\n" % str(e))
  851. msg = "[ERROR] An internal error has ocurred. See shell.\n"
  852. msg += 'FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s' % str(e)
  853. msg += traceback.format_exc()
  854. self.app.inform.emit(msg)
  855. return
  856. # Object initialization function for app.new_object()
  857. # RUNNING ON SEPARATE THREAD!
  858. def job_init(job_obj, app_obj):
  859. assert isinstance(job_obj, FlatCAMCNCjob), \
  860. "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
  861. # count the tools
  862. tool_cnt = 0
  863. dia_cnc_dict = {}
  864. # this turn on the FlatCAMCNCJob plot for multiple tools
  865. job_obj.multitool = True
  866. job_obj.multigeo = True
  867. job_obj.cnc_tools.clear()
  868. job_obj.options['xmin'] = xmin
  869. job_obj.options['ymin'] = ymin
  870. job_obj.options['xmax'] = xmax
  871. job_obj.options['ymax'] = ymax
  872. # try:
  873. # job_obj.feedrate_probe = float(self.options["feedrate_probe"])
  874. # except ValueError:
  875. # # try to convert comma to decimal point. if it's still not working error message and return
  876. # try:
  877. # job_obj.feedrate_rapid = float(self.options["feedrate_probe"].replace(',', '.'))
  878. # except ValueError:
  879. # self.app.inform.emit(
  880. # '[ERROR_NOTCL]Wrong value format for self.defaults["feedrate_probe"] '
  881. # 'or self.options["feedrate_probe"]')
  882. # make sure that trying to make a CNCJob from an empty file is not creating an app crash
  883. a = 0
  884. for tooluid_key in self.tools:
  885. if self.tools[tooluid_key]['solid_geometry'] is None:
  886. a += 1
  887. if a == len(self.tools):
  888. self.app.inform.emit('[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...')
  889. return 'fail'
  890. for tooluid_key in self.tools:
  891. tool_cnt += 1
  892. app_obj.progress.emit(20)
  893. # find the tool_dia associated with the tooluid_key
  894. tool_dia = self.sel_tools[tooluid_key]['tooldia']
  895. tool_solid_geometry = self.tools[tooluid_key]['solid_geometry']
  896. for diadict_key, diadict_value in self.sel_tools[tooluid_key].items():
  897. if diadict_key == 'tooldia':
  898. tooldia_val = float('%.4f' % float(diadict_value))
  899. dia_cnc_dict.update({
  900. diadict_key: tooldia_val
  901. })
  902. if diadict_key == 'offset':
  903. dia_cnc_dict.update({
  904. diadict_key: ''
  905. })
  906. if diadict_key == 'type':
  907. dia_cnc_dict.update({
  908. diadict_key: ''
  909. })
  910. if diadict_key == 'tool_type':
  911. dia_cnc_dict.update({
  912. diadict_key: ''
  913. })
  914. if diadict_key == 'data':
  915. for data_key, data_value in diadict_value.items():
  916. if data_key == "multidepth":
  917. multidepth = data_value
  918. if data_key == "depthperpass":
  919. depthpercut = data_value
  920. if data_key == "extracut":
  921. extracut = data_value
  922. if data_key == "startz":
  923. startz = data_value
  924. if data_key == "endz":
  925. endz = data_value
  926. if data_key == "toolchangez":
  927. toolchangez = data_value
  928. if data_key == "toolchangexy":
  929. toolchangexy = data_value
  930. if data_key == "toolchange":
  931. toolchange = data_value
  932. if data_key == "cutz":
  933. z_cut = data_value
  934. if data_key == "travelz":
  935. z_move = data_value
  936. if data_key == "feedrate":
  937. feedrate = data_value
  938. if data_key == "feedrate_z":
  939. feedrate_z = data_value
  940. if data_key == "feedrate_rapid":
  941. feedrate_rapid = data_value
  942. if data_key == "ppname_g":
  943. pp_geometry_name = data_value
  944. if data_key == "spindlespeed":
  945. spindlespeed = data_value
  946. if data_key == "dwell":
  947. dwell = data_value
  948. if data_key == "dwelltime":
  949. dwelltime = data_value
  950. datadict = copy.deepcopy(diadict_value)
  951. dia_cnc_dict.update({
  952. diadict_key: datadict
  953. })
  954. job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
  955. job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
  956. # Propagate options
  957. job_obj.options["tooldia"] = tooldia_val
  958. job_obj.options['type'] = 'Geometry'
  959. job_obj.options['tool_dia'] = tooldia_val
  960. app_obj.progress.emit(40)
  961. res = job_obj.generate_from_multitool_geometry(
  962. tool_solid_geometry, tooldia=tooldia_val, offset=0.0,
  963. tolerance=0.0005, z_cut=z_cut, z_move=z_move,
  964. feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
  965. spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
  966. multidepth=multidepth, depthpercut=depthpercut,
  967. extracut=extracut, startz=startz, endz=endz,
  968. toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
  969. pp_geometry_name=pp_geometry_name,
  970. tool_no=tool_cnt)
  971. if res == 'fail':
  972. log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
  973. return 'fail'
  974. else:
  975. dia_cnc_dict['gcode'] = res
  976. dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
  977. # TODO this serve for bounding box creation only; should be optimized
  978. dia_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_cnc_dict['gcode_parsed']])
  979. # tell gcode_parse from which point to start drawing the lines depending on what kind of
  980. # object is the source of gcode
  981. job_obj.toolchange_xy_type = "geometry"
  982. app_obj.progress.emit(80)
  983. job_obj.cnc_tools.update({
  984. tooluid_key: copy.deepcopy(dia_cnc_dict)
  985. })
  986. dia_cnc_dict.clear()
  987. if use_thread:
  988. # To be run in separate thread
  989. # The idea is that if there is a solid_geometry in the file "root" then most likely thare are no
  990. # separate solid_geometry in the self.tools dictionary
  991. def job_thread(app_obj):
  992. with self.app.proc_container.new("Generating CNC Code"):
  993. if app_obj.new_object("cncjob", outname, job_init) != 'fail':
  994. app_obj.inform.emit("[success]ToolSolderPaste CNCjob created: %s" % outname)
  995. app_obj.progress.emit(100)
  996. # Create a promise with the name
  997. self.app.collection.promise(outname)
  998. # Send to worker
  999. self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
  1000. else:
  1001. self.app.new_object("cncjob", outname, job_init)
  1002. def reset_fields(self):
  1003. self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  1004. self.geo_obj_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
  1005. self.cnc_obj_combo.setRootModelIndex(self.app.collection.index(3, 0, QtCore.QModelIndex()))