ToolSolderPaste.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  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 FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
  8. class ToolSolderPaste(FlatCAMTool):
  9. toolName = "Solder Paste Tool"
  10. def __init__(self, app):
  11. FlatCAMTool.__init__(self, app)
  12. ## Title
  13. title_label = QtWidgets.QLabel("%s" % self.toolName)
  14. title_label.setStyleSheet("""
  15. QLabel
  16. {
  17. font-size: 16px;
  18. font-weight: bold;
  19. }
  20. """)
  21. self.layout.addWidget(title_label)
  22. ## Form Layout
  23. obj_form_layout = QtWidgets.QFormLayout()
  24. self.layout.addLayout(obj_form_layout)
  25. ## Gerber Object to be used for solderpaste dispensing
  26. self.obj_combo = QtWidgets.QComboBox()
  27. self.obj_combo.setModel(self.app.collection)
  28. self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  29. self.obj_combo.setCurrentIndex(1)
  30. self.object_label = QtWidgets.QLabel("Gerber: ")
  31. self.object_label.setToolTip(
  32. "Gerber Solder paste object. "
  33. )
  34. obj_form_layout.addRow(self.object_label, self.obj_combo)
  35. #### Tools ####
  36. self.tools_table_label = QtWidgets.QLabel('<b>Tools Table</b>')
  37. self.tools_table_label.setToolTip(
  38. "Tools pool from which the algorithm\n"
  39. "will pick the ones used for dispensing solder paste."
  40. )
  41. self.layout.addWidget(self.tools_table_label)
  42. self.tools_table = FCTable()
  43. self.layout.addWidget(self.tools_table)
  44. self.tools_table.setColumnCount(3)
  45. self.tools_table.setHorizontalHeaderLabels(['#', 'Diameter', ''])
  46. self.tools_table.setColumnHidden(2, True)
  47. self.tools_table.setSortingEnabled(False)
  48. # self.tools_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
  49. self.tools_table.horizontalHeaderItem(0).setToolTip(
  50. "This is the Tool Number.\n"
  51. "The solder dispensing will start with the tool with the biggest \n"
  52. "diameter, continuing until there are no more Nozzle tools.\n"
  53. "If there are no longer tools but there are still pads not covered\n "
  54. "with solder paste, the app will issue a warning message box."
  55. )
  56. self.tools_table.horizontalHeaderItem(1).setToolTip(
  57. "Nozzle tool Diameter. It's value (in current FlatCAM units)\n"
  58. "is the width of the solder paste dispensed.")
  59. self.empty_label = QtWidgets.QLabel('')
  60. self.layout.addWidget(self.empty_label)
  61. #### Add a new Tool ####
  62. hlay_tools = QtWidgets.QHBoxLayout()
  63. self.layout.addLayout(hlay_tools)
  64. self.addtool_entry_lbl = QtWidgets.QLabel('<b>Nozzle Dia:</b>')
  65. self.addtool_entry_lbl.setToolTip(
  66. "Diameter for the new Nozzle tool to add in the Tool Table"
  67. )
  68. self.addtool_entry = FCEntry()
  69. # hlay.addWidget(self.addtool_label)
  70. # hlay.addStretch()
  71. hlay_tools.addWidget(self.addtool_entry_lbl)
  72. hlay_tools.addWidget(self.addtool_entry)
  73. grid0 = QtWidgets.QGridLayout()
  74. self.layout.addLayout(grid0)
  75. self.addtool_btn = QtWidgets.QPushButton('Add')
  76. self.addtool_btn.setToolTip(
  77. "Add a new nozzle tool to the Tool Table\n"
  78. "with the diameter specified above."
  79. )
  80. self.deltool_btn = QtWidgets.QPushButton('Delete')
  81. self.deltool_btn.setToolTip(
  82. "Delete a selection of tools in the Tool Table\n"
  83. "by first selecting a row(s) in the Tool Table."
  84. )
  85. self.soldergeo_btn = QtWidgets.QPushButton("Generate Geo")
  86. self.soldergeo_btn.setToolTip(
  87. "Generate solder paste dispensing geometry."
  88. )
  89. grid0.addWidget(self.addtool_btn, 0, 0)
  90. # grid2.addWidget(self.copytool_btn, 0, 1)
  91. grid0.addWidget(self.deltool_btn, 0, 2)
  92. grid0.addWidget(self.soldergeo_btn, 2, 2)
  93. ## Form Layout
  94. geo_form_layout = QtWidgets.QFormLayout()
  95. self.layout.addLayout(geo_form_layout)
  96. ## Gerber Object to be used for solderpaste dispensing
  97. self.geo_obj_combo = QtWidgets.QComboBox()
  98. self.geo_obj_combo.setModel(self.app.collection)
  99. self.geo_obj_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
  100. self.geo_obj_combo.setCurrentIndex(1)
  101. self.geo_object_label = QtWidgets.QLabel("Geometry:")
  102. self.geo_object_label.setToolTip(
  103. "Geometry Solder paste object.\n"
  104. "In order to enable the GCode generation section,\n"
  105. "the name of the object has to end in:\n"
  106. "'_solderpaste' as a protection."
  107. )
  108. geo_form_layout.addRow(self.geo_object_label, self.geo_obj_combo)
  109. self.gcode_frame = QtWidgets.QFrame()
  110. self.gcode_frame.setContentsMargins(0, 0, 0, 0)
  111. self.layout.addWidget(self.gcode_frame)
  112. self.gcode_box = QtWidgets.QVBoxLayout()
  113. self.gcode_box.setContentsMargins(0, 0, 0, 0)
  114. self.gcode_frame.setLayout(self.gcode_box)
  115. ## Form Layout
  116. form_layout = QtWidgets.QFormLayout()
  117. self.gcode_box.addLayout(form_layout)
  118. # Z dispense start
  119. self.z_start_entry = FCEntry()
  120. self.z_start_label = QtWidgets.QLabel("Z Dispense Start:")
  121. self.z_start_label.setToolTip(
  122. "The size of the gaps in the cutout\n"
  123. "used to keep the board connected to\n"
  124. "the surrounding material (the one \n"
  125. "from which the PCB is cutout)."
  126. )
  127. form_layout.addRow(self.z_start_label, self.z_start_entry)
  128. # Z dispense
  129. self.z_dispense_entry = FCEntry()
  130. self.z_dispense_label = QtWidgets.QLabel("Z Dispense:")
  131. self.z_dispense_label.setToolTip(
  132. "Margin over bounds. A positive value here\n"
  133. "will make the cutout of the PCB further from\n"
  134. "the actual PCB border"
  135. )
  136. form_layout.addRow(self.z_dispense_label, self.z_dispense_entry)
  137. # Z dispense stop
  138. self.z_stop_entry = FCEntry()
  139. self.z_stop_label = QtWidgets.QLabel("Z Dispense Stop:")
  140. self.z_stop_label.setToolTip(
  141. "The size of the gaps in the cutout\n"
  142. "used to keep the board connected to\n"
  143. "the surrounding material (the one \n"
  144. "from which the PCB is cutout)."
  145. )
  146. 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 size of the gaps in the cutout\n"
  152. "used to keep the board connected to\n"
  153. "the surrounding material (the one \n"
  154. "from which the PCB is cutout)."
  155. )
  156. form_layout.addRow(self.z_travel_label, self.z_travel_entry)
  157. # Feedrate X-Y
  158. self.frxy_entry = FCEntry()
  159. self.frxy_label = QtWidgets.QLabel("Feedrate X-Y:")
  160. self.frxy_label.setToolTip(
  161. "The size of the gaps in the cutout\n"
  162. "used to keep the board connected to\n"
  163. "the surrounding material (the one \n"
  164. "from which the PCB is cutout)."
  165. )
  166. form_layout.addRow(self.frxy_label, self.frxy_entry)
  167. # Feedrate Z
  168. self.frz_entry = FCEntry()
  169. self.frz_label = QtWidgets.QLabel("Feedrate Z:")
  170. self.frz_label.setToolTip(
  171. "The size of the gaps in the cutout\n"
  172. "used to keep the board connected to\n"
  173. "the surrounding material (the one \n"
  174. "from which the PCB is cutout)."
  175. )
  176. form_layout.addRow(self.frz_label, self.frz_entry)
  177. # Spindle Speed Forward
  178. self.speedfwd_entry = FCEntry()
  179. self.speedfwd_label = QtWidgets.QLabel("Spindle Speed FWD:")
  180. self.speedfwd_label.setToolTip(
  181. "The size of the gaps in the cutout\n"
  182. "used to keep the board connected to\n"
  183. "the surrounding material (the one \n"
  184. "from which the PCB is cutout)."
  185. )
  186. form_layout.addRow(self.speedfwd_label, self.speedfwd_entry)
  187. # Dwell Forward
  188. self.dwellfwd_entry = FCEntry()
  189. self.dwellfwd_label = QtWidgets.QLabel("Dwell FWD:")
  190. self.dwellfwd_label.setToolTip(
  191. "The size of the gaps in the cutout\n"
  192. "used to keep the board connected to\n"
  193. "the surrounding material (the one \n"
  194. "from which the PCB is cutout)."
  195. )
  196. form_layout.addRow(self.dwellfwd_label, self.dwellfwd_entry)
  197. # Spindle Speed Reverse
  198. self.speedrev_entry = FCEntry()
  199. self.speedrev_label = QtWidgets.QLabel("Spindle Speed REV:")
  200. self.speedrev_label.setToolTip(
  201. "The size of the gaps in the cutout\n"
  202. "used to keep the board connected to\n"
  203. "the surrounding material (the one \n"
  204. "from which the PCB is cutout)."
  205. )
  206. form_layout.addRow(self.speedrev_label, self.speedrev_entry)
  207. # Dwell Reverse
  208. self.dwellrev_entry = FCEntry()
  209. self.dwellrev_label = QtWidgets.QLabel("Dwell REV:")
  210. self.dwellrev_label.setToolTip(
  211. "The size of the gaps in the cutout\n"
  212. "used to keep the board connected to\n"
  213. "the surrounding material (the one \n"
  214. "from which the PCB is cutout)."
  215. )
  216. form_layout.addRow(self.dwellrev_label, self.dwellrev_entry)
  217. # Postprocessors
  218. pp_label = QtWidgets.QLabel('PostProcessors:')
  219. pp_label.setToolTip(
  220. "Files that control the GCoe generation."
  221. )
  222. self.pp_combo = FCComboBox()
  223. pp_items = [1, 2, 3, 4, 5]
  224. for it in pp_items:
  225. self.pp_combo.addItem(str(it))
  226. self.pp_combo.setStyleSheet('background-color: rgb(255,255,255)')
  227. form_layout.addRow(pp_label, self.pp_combo)
  228. ## Buttons
  229. hlay = QtWidgets.QHBoxLayout()
  230. self.gcode_box.addLayout(hlay)
  231. hlay.addStretch()
  232. self.solder_gcode = QtWidgets.QPushButton("Generate GCode")
  233. self.solder_gcode.setToolTip(
  234. "Generate GCode to dispense Solder Paste\n"
  235. "on PCB pads."
  236. )
  237. hlay.addWidget(self.solder_gcode)
  238. self.layout.addStretch()
  239. self.gcode_frame.setDisabled(True)
  240. self.tools = {}
  241. self.tooluid = 0
  242. ## Signals
  243. self.addtool_btn.clicked.connect(self.on_tool_add)
  244. self.deltool_btn.clicked.connect(self.on_tool_delete)
  245. self.soldergeo_btn.clicked.connect(self.on_create_geo)
  246. self.solder_gcode.clicked.connect(self.on_create_gcode)
  247. self.geo_obj_combo.currentIndexChanged.connect(self.on_geo_select)
  248. def run(self):
  249. self.app.report_usage("ToolSolderPaste()")
  250. FlatCAMTool.run(self)
  251. self.set_tool_ui()
  252. # if the splitter us hidden, display it
  253. if self.app.ui.splitter.sizes()[0] == 0:
  254. self.app.ui.splitter.setSizes([1, 1])
  255. self.build_ui()
  256. self.app.ui.notebook.setTabText(2, "SolderPaste Tool")
  257. def install(self, icon=None, separator=None, **kwargs):
  258. FlatCAMTool.install(self, icon, separator, shortcut='ALT+K', **kwargs)
  259. def set_tool_ui(self):
  260. # self.ncc_overlap_entry.set_value(self.app.defaults["tools_nccoverlap"])
  261. # self.ncc_margin_entry.set_value(self.app.defaults["tools_nccmargin"])
  262. # self.ncc_method_radio.set_value(self.app.defaults["tools_nccmethod"])
  263. # self.ncc_connect_cb.set_value(self.app.defaults["tools_nccconnect"])
  264. # self.ncc_contour_cb.set_value(self.app.defaults["tools_ncccontour"])
  265. # self.ncc_rest_cb.set_value(self.app.defaults["tools_nccrest"])
  266. self.tools_table.setupContextMenu()
  267. self.tools_table.addContextMenu(
  268. "Add", lambda: self.on_tool_add(dia=None, muted=None), icon=QtGui.QIcon("share/plus16.png"))
  269. self.tools_table.addContextMenu(
  270. "Delete", lambda:
  271. self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
  272. try:
  273. dias = [float(eval(dia)) for dia in self.app.defaults["tools_solderpaste_tools"].split(",")]
  274. except:
  275. log.error("At least one Nozzle tool diameter needed. "
  276. "Verify in Edit -> Preferences -> TOOLS -> Solder Paste Tools.")
  277. return
  278. self.tooluid = 0
  279. self.tools.clear()
  280. for tool_dia in dias:
  281. self.tooluid += 1
  282. self.tools.update({
  283. int(self.tooluid): {
  284. 'tooldia': float('%.4f' % tool_dia),
  285. 'solid_geometry': []
  286. }
  287. })
  288. self.name = ""
  289. self.obj = None
  290. self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
  291. self.reset_fields()
  292. def build_ui(self):
  293. self.ui_disconnect()
  294. # updated units
  295. self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
  296. if self.units == "IN":
  297. self.addtool_entry.set_value(0.039)
  298. else:
  299. self.addtool_entry.set_value(1)
  300. sorted_tools = []
  301. for k, v in self.tools.items():
  302. sorted_tools.append(float('%.4f' % float(v['tooldia'])))
  303. sorted_tools.sort(reverse=True)
  304. n = len(sorted_tools)
  305. self.tools_table.setRowCount(n)
  306. tool_id = 0
  307. for tool_sorted in sorted_tools:
  308. for tooluid_key, tooluid_value in self.tools.items():
  309. if float('%.4f' % tooluid_value['tooldia']) == tool_sorted:
  310. tool_id += 1
  311. id = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
  312. id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  313. row_no = tool_id - 1
  314. self.tools_table.setItem(row_no, 0, id) # Tool name/id
  315. # Make sure that the drill diameter when in MM is with no more than 2 decimals
  316. # There are no drill bits in MM with more than 3 decimals diameter
  317. # For INCH the decimals should be no more than 3. There are no drills under 10mils
  318. if self.units == 'MM':
  319. dia = QtWidgets.QTableWidgetItem('%.2f' % tooluid_value['tooldia'])
  320. else:
  321. dia = QtWidgets.QTableWidgetItem('%.3f' % tooluid_value['tooldia'])
  322. dia.setFlags(QtCore.Qt.ItemIsEnabled)
  323. tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
  324. self.tools_table.setItem(row_no, 1, dia) # Diameter
  325. self.tools_table.setItem(row_no, 2, tool_uid_item) # Tool unique ID
  326. # make the diameter column editable
  327. for row in range(tool_id):
  328. self.tools_table.item(row, 1).setFlags(
  329. QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  330. # all the tools are selected by default
  331. self.tools_table.selectColumn(0)
  332. #
  333. self.tools_table.resizeColumnsToContents()
  334. self.tools_table.resizeRowsToContents()
  335. vertical_header = self.tools_table.verticalHeader()
  336. vertical_header.hide()
  337. self.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
  338. horizontal_header = self.tools_table.horizontalHeader()
  339. horizontal_header.setMinimumSectionSize(10)
  340. horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
  341. horizontal_header.resizeSection(0, 20)
  342. horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
  343. # self.tools_table.setSortingEnabled(True)
  344. # sort by tool diameter
  345. # self.tools_table.sortItems(1)
  346. self.tools_table.setMinimumHeight(self.tools_table.getHeight())
  347. self.tools_table.setMaximumHeight(self.tools_table.getHeight())
  348. self.ui_connect()
  349. def ui_connect(self):
  350. self.tools_table.itemChanged.connect(self.on_tool_edit)
  351. def ui_disconnect(self):
  352. try:
  353. # if connected, disconnect the signal from the slot on item_changed as it creates issues
  354. self.tools_table.itemChanged.disconnect(self.on_tool_edit)
  355. except:
  356. pass
  357. def on_tool_add(self, dia=None, muted=None):
  358. self.ui_disconnect()
  359. if dia:
  360. tool_dia = dia
  361. else:
  362. try:
  363. tool_dia = float(self.addtool_entry.get_value())
  364. except ValueError:
  365. # try to convert comma to decimal point. if it's still not working error message and return
  366. try:
  367. tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
  368. except ValueError:
  369. self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
  370. "use a number.")
  371. return
  372. if tool_dia is None:
  373. self.build_ui()
  374. self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter to add, in Float format.")
  375. return
  376. if tool_dia == 0:
  377. self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.")
  378. return
  379. # construct a list of all 'tooluid' in the self.tools
  380. tool_uid_list = []
  381. for tooluid_key in self.tools:
  382. tool_uid_item = int(tooluid_key)
  383. tool_uid_list.append(tool_uid_item)
  384. # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
  385. if not tool_uid_list:
  386. max_uid = 0
  387. else:
  388. max_uid = max(tool_uid_list)
  389. self.tooluid = int(max_uid + 1)
  390. tool_dias = []
  391. for k, v in self.tools.items():
  392. for tool_v in v.keys():
  393. if tool_v == 'tooldia':
  394. tool_dias.append(float('%.4f' % v[tool_v]))
  395. if float('%.4f' % tool_dia) in tool_dias:
  396. if muted is None:
  397. self.app.inform.emit("[WARNING_NOTCL]Adding Nozzle tool cancelled. Tool already in Tool Table.")
  398. self.tools_table.itemChanged.connect(self.on_tool_edit)
  399. return
  400. else:
  401. if muted is None:
  402. self.app.inform.emit("[success] New Nozzle tool added to Tool Table.")
  403. self.tools.update({
  404. int(self.tooluid): {
  405. 'tooldia': float('%.4f' % tool_dia),
  406. 'solid_geometry': []
  407. }
  408. })
  409. self.build_ui()
  410. def on_tool_edit(self):
  411. self.ui_disconnect()
  412. tool_dias = []
  413. for k, v in self.tools.items():
  414. for tool_v in v.keys():
  415. if tool_v == 'tooldia':
  416. tool_dias.append(float('%.4f' % v[tool_v]))
  417. for row in range(self.tools_table.rowCount()):
  418. try:
  419. new_tool_dia = float(self.tools_table.item(row, 1).text())
  420. except ValueError:
  421. # try to convert comma to decimal point. if it's still not working error message and return
  422. try:
  423. new_tool_dia = float(self.tools_table.item(row, 1).text().replace(',', '.'))
  424. except ValueError:
  425. self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
  426. "use a number.")
  427. return
  428. tooluid = int(self.tools_table.item(row, 2).text())
  429. # identify the tool that was edited and get it's tooluid
  430. if new_tool_dia not in tool_dias:
  431. self.tools[tooluid]['tooldia'] = new_tool_dia
  432. self.app.inform.emit("[success] Nozzle tool from Tool Table was edited.")
  433. self.build_ui()
  434. return
  435. else:
  436. # identify the old tool_dia and restore the text in tool table
  437. for k, v in self.tools.items():
  438. if k == tooluid:
  439. old_tool_dia = v['tooldia']
  440. break
  441. restore_dia_item = self.tools_table.item(row, 1)
  442. restore_dia_item.setText(str(old_tool_dia))
  443. self.app.inform.emit("[WARNING_NOTCL] Edit cancelled. New diameter value is already in the Tool Table.")
  444. self.build_ui()
  445. def on_tool_delete(self, rows_to_delete=None, all=None):
  446. self.ui_disconnect()
  447. deleted_tools_list = []
  448. if all:
  449. self.tools.clear()
  450. self.build_ui()
  451. return
  452. if rows_to_delete:
  453. try:
  454. for row in rows_to_delete:
  455. tooluid_del = int(self.tools_table.item(row, 2).text())
  456. deleted_tools_list.append(tooluid_del)
  457. except TypeError:
  458. deleted_tools_list.append(rows_to_delete)
  459. for t in deleted_tools_list:
  460. self.tools.pop(t, None)
  461. self.build_ui()
  462. return
  463. try:
  464. if self.tools_table.selectedItems():
  465. for row_sel in self.tools_table.selectedItems():
  466. row = row_sel.row()
  467. if row < 0:
  468. continue
  469. tooluid_del = int(self.tools_table.item(row, 2).text())
  470. deleted_tools_list.append(tooluid_del)
  471. for t in deleted_tools_list:
  472. self.tools.pop(t, None)
  473. except AttributeError:
  474. self.app.inform.emit("[WARNING_NOTCL]Delete failed. Select a Nozzle tool to delete.")
  475. return
  476. except Exception as e:
  477. log.debug(str(e))
  478. self.app.inform.emit("[success] Nozzle tool(s) deleted from Tool Table.")
  479. self.build_ui()
  480. def on_geo_select(self):
  481. if self.geo_obj_combo.currentText().rpartition('_')[2] == 'solderpaste':
  482. self.gcode_frame.setDisabled(False)
  483. else:
  484. self.gcode_frame.setDisabled(True)
  485. @staticmethod
  486. def distance(pt1, pt2):
  487. return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
  488. def on_create_geo(self):
  489. proc = self.app.proc_container.new("Creating Solder Paste dispensing geometry.")
  490. name = self.obj_combo.currentText()
  491. obj = self.app.collection.get_by_name(name)
  492. if type(obj.solid_geometry) is not list:
  493. obj.solid_geometry = [obj.solid_geometry]
  494. # Sort tools in descending order
  495. sorted_tools = []
  496. for k, v in self.tools.items():
  497. sorted_tools.append(float('%.4f' % float(v['tooldia'])))
  498. sorted_tools.sort(reverse=True)
  499. def geo_init(geo_obj, app_obj):
  500. geo_obj.solid_geometry = []
  501. geo_obj.tools = {}
  502. geo_obj.multigeo = True
  503. geo_obj.multitool = True
  504. geo_obj.tools = {}
  505. def solder_line(p, offset):
  506. xmin, ymin, xmax, ymax = p.bounds
  507. min = [xmin, ymin]
  508. max = [xmax, ymax]
  509. min_r = [xmin, ymax]
  510. max_r = [xmax, ymin]
  511. diagonal_1 = LineString([min, max])
  512. diagonal_2 = LineString([min_r, max_r])
  513. round_diag_1 = round(diagonal_1.intersection(p).length, 4)
  514. round_diag_2 = round(diagonal_2.intersection(p).length, 4)
  515. if round_diag_1 == round_diag_2:
  516. l = distance((xmin, ymin), (xmax, ymin))
  517. h = distance((xmin, ymin), (xmin, ymax))
  518. if offset >= l /2 or offset >= h / 2:
  519. return "fail"
  520. if l > h:
  521. h_half = h / 2
  522. start = [xmin, (ymin + h_half)]
  523. stop = [(xmin + l), (ymin + h_half)]
  524. else:
  525. l_half = l / 2
  526. start = [(xmin + l_half), ymin]
  527. stop = [(xmin + l_half), (ymin + h)]
  528. geo = LineString([start, stop])
  529. elif round_diag_1 > round_diag_2:
  530. geo = diagonal_1.intersection(p)
  531. else:
  532. geo = diagonal_2.intersection(p)
  533. offseted_poly = p.buffer(-offset)
  534. geo = geo.intersection(offseted_poly)
  535. return geo
  536. work_geo = obj.solid_geometry
  537. rest_geo = []
  538. tooluid = 1
  539. for tool in sorted_tools:
  540. offset = tool / 2
  541. for uid, v in self.tools.items():
  542. if float('%.4f' % float(v['tooldia'])) == tool:
  543. tooluid = int(uid)
  544. break
  545. for g in work_geo:
  546. if type(g) == MultiPolygon:
  547. for poly in g:
  548. geom = solder_line(poly, offset=offset)
  549. if geom != 'fail':
  550. try:
  551. geo_obj.tools[tooluid]['solid_geometry'].append(geom)
  552. except KeyError:
  553. geo_obj.tools[tooluid] = {}
  554. geo_obj.tools[tooluid]['solid_geometry'] = []
  555. geo_obj.tools[tooluid]['solid_geometry'].append(geom)
  556. geo_obj.tools[tooluid]['tooldia'] = tool
  557. geo_obj.tools[tooluid]['offset'] = 'Path'
  558. geo_obj.tools[tooluid]['offset_value'] = 0.0
  559. geo_obj.tools[tooluid]['type'] = ' '
  560. geo_obj.tools[tooluid]['tool_type'] = ' '
  561. geo_obj.tools[tooluid]['data'] = {}
  562. else:
  563. rest_geo.append(poly)
  564. elif type(g) == Polygon:
  565. geom = solder_line(g, offset=offset)
  566. if geom != 'fail':
  567. try:
  568. geo_obj.tools[tooluid]['solid_geometry'].append(geom)
  569. except KeyError:
  570. geo_obj.tools[tooluid] = {}
  571. geo_obj.tools[tooluid]['solid_geometry'] = []
  572. geo_obj.tools[tooluid]['solid_geometry'].append(geom)
  573. geo_obj.tools[tooluid]['tooldia'] = tool
  574. geo_obj.tools[tooluid]['offset'] = 'Path'
  575. geo_obj.tools[tooluid]['offset_value'] = 0.0
  576. geo_obj.tools[tooluid]['type'] = ' '
  577. geo_obj.tools[tooluid]['tool_type'] = ' '
  578. geo_obj.tools[tooluid]['data'] = {}
  579. else:
  580. rest_geo.append(g)
  581. work_geo = rest_geo
  582. if not work_geo:
  583. app_obj.inform.emit("[success] Solder Paste geometry generated successfully...")
  584. return
  585. # if we still have geometry not processed at the end of the tools then we failed
  586. # some or all the pads are not covered with solder paste
  587. if rest_geo:
  588. app_obj.inform.emit("[WARNING_NOTCL] Some or all pads have no solder "
  589. "due of inadequate nozzle diameters...")
  590. return 'fail'
  591. def job_thread(app_obj):
  592. try:
  593. app_obj.new_object("geometry", name + "_solderpaste", geo_init)
  594. except Exception as e:
  595. proc.done()
  596. traceback.print_stack()
  597. return
  598. proc.done()
  599. self.app.inform.emit("Generating Solder Paste dispensing geometry...")
  600. # Promise object with the new name
  601. self.app.collection.promise(name)
  602. # Background
  603. self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
  604. # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
  605. def on_create_gcode(self):
  606. name = self.obj_combo.currentText()
  607. def geo_init(geo_obj, app_obj):
  608. pass
  609. # self.app.new_object("geometry", name + "_cutout", geo_init)
  610. # self.app.inform.emit("[success] Rectangular CutOut operation finished.")
  611. # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
  612. def reset_fields(self):
  613. self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))