FlatCAMDB.py 100 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399
  1. from PyQt5 import QtGui, QtCore, QtWidgets
  2. from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
  3. FCTree, RadioSet, FCFileSaveDialog
  4. from camlib import to_dict
  5. import sys
  6. import json
  7. from copy import deepcopy
  8. from datetime import datetime
  9. import gettext
  10. import FlatCAMTranslation as fcTranslate
  11. import builtins
  12. fcTranslate.apply_language('strings')
  13. if '_' not in builtins.__dict__:
  14. _ = gettext.gettext
  15. class ToolsDB(QtWidgets.QWidget):
  16. mark_tools_rows = QtCore.pyqtSignal()
  17. def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
  18. super(ToolsDB, self).__init__(parent)
  19. self.app = app
  20. self.decimals = 4
  21. self.callback_app = callback_on_edited
  22. self.on_tool_request = callback_on_tool_request
  23. self.offset_item_options = ["Path", "In", "Out", "Custom"]
  24. self.type_item_options = ["Iso", "Rough", "Finish"]
  25. self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
  26. '''
  27. dict to hold all the tools in the Tools DB
  28. format:
  29. {
  30. tool_id: {
  31. 'name': 'new_tool'
  32. 'tooldia': self.app.defaults["geometry_cnctooldia"]
  33. 'offset': 'Path'
  34. 'offset_value': 0.0
  35. 'type': _('Rough'),
  36. 'tool_type': 'C1'
  37. 'data': dict()
  38. }
  39. }
  40. '''
  41. self.db_tool_dict = {}
  42. # layouts
  43. layout = QtWidgets.QVBoxLayout()
  44. self.setLayout(layout)
  45. table_hlay = QtWidgets.QHBoxLayout()
  46. layout.addLayout(table_hlay)
  47. self.table_widget = FCTable(drag_drop=True)
  48. self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
  49. table_hlay.addWidget(self.table_widget)
  50. # set the number of columns and the headers tool tips
  51. self.configure_table()
  52. # pal = QtGui.QPalette()
  53. # pal.setColor(QtGui.QPalette.Background, Qt.white)
  54. # New Bookmark
  55. new_vlay = QtWidgets.QVBoxLayout()
  56. layout.addLayout(new_vlay)
  57. # new_tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Tool"))
  58. # new_vlay.addWidget(new_tool_lbl, alignment=QtCore.Qt.AlignBottom)
  59. self.buttons_frame = QtWidgets.QFrame()
  60. self.buttons_frame.setContentsMargins(0, 0, 0, 0)
  61. layout.addWidget(self.buttons_frame)
  62. self.buttons_box = QtWidgets.QHBoxLayout()
  63. self.buttons_box.setContentsMargins(0, 0, 0, 0)
  64. self.buttons_frame.setLayout(self.buttons_box)
  65. self.buttons_frame.show()
  66. add_entry_btn = FCButton(_("Add Geometry Tool in DB"))
  67. add_entry_btn.setToolTip(
  68. _("Add a new tool in the Tools Database.\n"
  69. "It will be used in the Geometry UI.\n"
  70. "You can edit it after it is added.")
  71. )
  72. self.buttons_box.addWidget(add_entry_btn)
  73. # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
  74. # add_fct_entry_btn.setToolTip(
  75. # _("Add a new tool in the Tools Database.\n"
  76. # "It will be used in the Paint/NCC Tools UI.\n"
  77. # "You can edit it after it is added.")
  78. # )
  79. # self.buttons_box.addWidget(add_fct_entry_btn)
  80. remove_entry_btn = FCButton(_("Delete Tool from DB"))
  81. remove_entry_btn.setToolTip(
  82. _("Remove a selection of tools in the Tools Database.")
  83. )
  84. self.buttons_box.addWidget(remove_entry_btn)
  85. export_db_btn = FCButton(_("Export DB"))
  86. export_db_btn.setToolTip(
  87. _("Save the Tools Database to a custom text file.")
  88. )
  89. self.buttons_box.addWidget(export_db_btn)
  90. import_db_btn = FCButton(_("Import DB"))
  91. import_db_btn.setToolTip(
  92. _("Load the Tools Database information's from a custom text file.")
  93. )
  94. self.buttons_box.addWidget(import_db_btn)
  95. self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
  96. self.add_tool_from_db.setToolTip(
  97. _("Add a new tool in the Tools Table of the\n"
  98. "active Geometry object after selecting a tool\n"
  99. "in the Tools Database.")
  100. )
  101. self.add_tool_from_db.hide()
  102. self.cancel_tool_from_db = FCButton(_("Cancel"))
  103. self.cancel_tool_from_db.hide()
  104. hlay = QtWidgets.QHBoxLayout()
  105. layout.addLayout(hlay)
  106. hlay.addWidget(self.add_tool_from_db)
  107. hlay.addWidget(self.cancel_tool_from_db)
  108. hlay.addStretch()
  109. # ##############################################################################
  110. # ######################## SIGNALS #############################################
  111. # ##############################################################################
  112. add_entry_btn.clicked.connect(self.on_tool_add)
  113. remove_entry_btn.clicked.connect(self.on_tool_delete)
  114. export_db_btn.clicked.connect(self.on_export_tools_db_file)
  115. import_db_btn.clicked.connect(self.on_import_tools_db_file)
  116. # closebtn.clicked.connect(self.accept)
  117. self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
  118. self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
  119. self.setup_db_ui()
  120. def configure_table(self):
  121. self.table_widget.setColumnCount(27)
  122. # self.table_widget.setColumnWidth(0, 20)
  123. self.table_widget.setHorizontalHeaderLabels(
  124. [
  125. '#',
  126. _("Tool Name"),
  127. _("Tool Dia"),
  128. _("Tool Offset"),
  129. _("Custom Offset"),
  130. _("Tool Type"),
  131. _("Tool Shape"),
  132. _("Cut Z"),
  133. _("MultiDepth"),
  134. _("DPP"),
  135. _("V-Dia"),
  136. _("V-Angle"),
  137. _("Travel Z"),
  138. _("FR"),
  139. _("FR Z"),
  140. _("FR Rapids"),
  141. _("Spindle Speed"),
  142. _("Dwell"),
  143. _("Dwelltime"),
  144. _("Preprocessor"),
  145. _("ExtraCut"),
  146. _("E-Cut Length"),
  147. _("Toolchange"),
  148. _("Toolchange XY"),
  149. _("Toolchange Z"),
  150. _("Start Z"),
  151. _("End Z"),
  152. ]
  153. )
  154. self.table_widget.horizontalHeaderItem(0).setToolTip(
  155. _("Tool Index."))
  156. self.table_widget.horizontalHeaderItem(1).setToolTip(
  157. _("Tool name.\n"
  158. "This is not used in the app, it's function\n"
  159. "is to serve as a note for the user."))
  160. self.table_widget.horizontalHeaderItem(2).setToolTip(
  161. _("Tool Diameter."))
  162. self.table_widget.horizontalHeaderItem(3).setToolTip(
  163. _("Tool Offset.\n"
  164. "Can be of a few types:\n"
  165. "Path = zero offset\n"
  166. "In = offset inside by half of tool diameter\n"
  167. "Out = offset outside by half of tool diameter\n"
  168. "Custom = custom offset using the Custom Offset value"))
  169. self.table_widget.horizontalHeaderItem(4).setToolTip(
  170. _("Custom Offset.\n"
  171. "A value to be used as offset from the current path."))
  172. self.table_widget.horizontalHeaderItem(5).setToolTip(
  173. _("Tool Type.\n"
  174. "Can be:\n"
  175. "Iso = isolation cut\n"
  176. "Rough = rough cut, low feedrate, multiple passes\n"
  177. "Finish = finishing cut, high feedrate"))
  178. self.table_widget.horizontalHeaderItem(6).setToolTip(
  179. _("Tool Shape. \n"
  180. "Can be:\n"
  181. "C1 ... C4 = circular tool with x flutes\n"
  182. "B = ball tip milling tool\n"
  183. "V = v-shape milling tool"))
  184. self.table_widget.horizontalHeaderItem(7).setToolTip(
  185. _("Cutting Depth.\n"
  186. "The depth at which to cut into material."))
  187. self.table_widget.horizontalHeaderItem(8).setToolTip(
  188. _("Multi Depth.\n"
  189. "Selecting this will allow cutting in multiple passes,\n"
  190. "each pass adding a DPP parameter depth."))
  191. self.table_widget.horizontalHeaderItem(9).setToolTip(
  192. _("DPP. Depth per Pass.\n"
  193. "The value used to cut into material on each pass."))
  194. self.table_widget.horizontalHeaderItem(10).setToolTip(
  195. _("V-Dia.\n"
  196. "Diameter of the tip for V-Shape Tools."))
  197. self.table_widget.horizontalHeaderItem(11).setToolTip(
  198. _("V-Agle.\n"
  199. "Angle at the tip for the V-Shape Tools."))
  200. self.table_widget.horizontalHeaderItem(12).setToolTip(
  201. _("Clearance Height.\n"
  202. "Height at which the milling bit will travel between cuts,\n"
  203. "above the surface of the material, avoiding all fixtures."))
  204. self.table_widget.horizontalHeaderItem(13).setToolTip(
  205. _("FR. Feedrate\n"
  206. "The speed on XY plane used while cutting into material."))
  207. self.table_widget.horizontalHeaderItem(14).setToolTip(
  208. _("FR Z. Feedrate Z\n"
  209. "The speed on Z plane."))
  210. self.table_widget.horizontalHeaderItem(15).setToolTip(
  211. _("FR Rapids. Feedrate Rapids\n"
  212. "Speed used while moving as fast as possible.\n"
  213. "This is used only by some devices that can't use\n"
  214. "the G0 g-code command. Mostly 3D printers."))
  215. self.table_widget.horizontalHeaderItem(16).setToolTip(
  216. _("Spindle Speed.\n"
  217. "If it's left empty it will not be used.\n"
  218. "The speed of the spindle in RPM."))
  219. self.table_widget.horizontalHeaderItem(17).setToolTip(
  220. _("Dwell.\n"
  221. "Check this if a delay is needed to allow\n"
  222. "the spindle motor to reach it's set speed."))
  223. self.table_widget.horizontalHeaderItem(18).setToolTip(
  224. _("Dwell Time.\n"
  225. "A delay used to allow the motor spindle reach it's set speed."))
  226. self.table_widget.horizontalHeaderItem(19).setToolTip(
  227. _("Preprocessor.\n"
  228. "A selection of files that will alter the generated G-code\n"
  229. "to fit for a number of use cases."))
  230. self.table_widget.horizontalHeaderItem(20).setToolTip(
  231. _("Extra Cut.\n"
  232. "If checked, after a isolation is finished an extra cut\n"
  233. "will be added where the start and end of isolation meet\n"
  234. "such as that this point is covered by this extra cut to\n"
  235. "ensure a complete isolation."))
  236. self.table_widget.horizontalHeaderItem(21).setToolTip(
  237. _("Extra Cut length.\n"
  238. "If checked, after a isolation is finished an extra cut\n"
  239. "will be added where the start and end of isolation meet\n"
  240. "such as that this point is covered by this extra cut to\n"
  241. "ensure a complete isolation. This is the length of\n"
  242. "the extra cut."))
  243. self.table_widget.horizontalHeaderItem(22).setToolTip(
  244. _("Toolchange.\n"
  245. "It will create a toolchange event.\n"
  246. "The kind of toolchange is determined by\n"
  247. "the preprocessor file."))
  248. self.table_widget.horizontalHeaderItem(23).setToolTip(
  249. _("Toolchange XY.\n"
  250. "A set of coordinates in the format (x, y).\n"
  251. "Will determine the cartesian position of the point\n"
  252. "where the tool change event take place."))
  253. self.table_widget.horizontalHeaderItem(24).setToolTip(
  254. _("Toolchange Z.\n"
  255. "The position on Z plane where the tool change event take place."))
  256. self.table_widget.horizontalHeaderItem(25).setToolTip(
  257. _("Start Z.\n"
  258. "If it's left empty it will not be used.\n"
  259. "A position on Z plane to move immediately after job start."))
  260. self.table_widget.horizontalHeaderItem(26).setToolTip(
  261. _("End Z.\n"
  262. "A position on Z plane to move immediately after job stop."))
  263. def setup_db_ui(self):
  264. filename = self.app.data_path + '/geo_tools_db.FlatDB'
  265. # load the database tools from the file
  266. try:
  267. with open(filename) as f:
  268. tools = f.read()
  269. except IOError:
  270. self.app.log.error("Could not load tools DB file.")
  271. self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
  272. return
  273. try:
  274. self.db_tool_dict = json.loads(tools)
  275. except Exception:
  276. e = sys.exc_info()[0]
  277. self.app.log.error(str(e))
  278. self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
  279. return
  280. self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
  281. self.build_db_ui()
  282. self.table_widget.setupContextMenu()
  283. self.table_widget.addContextMenu(
  284. _("Add to DB"), self.on_tool_add, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png"))
  285. self.table_widget.addContextMenu(
  286. _("Copy from DB"), self.on_tool_copy, icon=QtGui.QIcon(self.app.resource_location + "/copy16.png"))
  287. self.table_widget.addContextMenu(
  288. _("Delete from DB"), self.on_tool_delete, icon=QtGui.QIcon(self.app.resource_location + "/delete32.png"))
  289. def build_db_ui(self):
  290. self.ui_disconnect()
  291. self.table_widget.setRowCount(len(self.db_tool_dict))
  292. nr_crt = 0
  293. for toolid, dict_val in self.db_tool_dict.items():
  294. row = nr_crt
  295. nr_crt += 1
  296. t_name = dict_val['name']
  297. try:
  298. self.add_tool_table_line(row, name=t_name, widget=self.table_widget, tooldict=dict_val)
  299. except Exception as e:
  300. self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
  301. vertical_header = self.table_widget.verticalHeader()
  302. vertical_header.hide()
  303. horizontal_header = self.table_widget.horizontalHeader()
  304. horizontal_header.setMinimumSectionSize(10)
  305. horizontal_header.setDefaultSectionSize(70)
  306. self.table_widget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
  307. for x in range(27):
  308. self.table_widget.resizeColumnToContents(x)
  309. horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
  310. # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
  311. # horizontal_header.setSectionResizeMode(13, QtWidgets.QHeaderView.Fixed)
  312. horizontal_header.resizeSection(0, 20)
  313. # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
  314. # horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
  315. self.ui_connect()
  316. def add_tool_table_line(self, row, name, widget, tooldict):
  317. data = tooldict['data']
  318. nr_crt = row + 1
  319. id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt))
  320. # id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  321. flags = id_item.flags() & ~QtCore.Qt.ItemIsEditable
  322. id_item.setFlags(flags)
  323. widget.setItem(row, 0, id_item) # Tool name/id
  324. tool_name_item = QtWidgets.QTableWidgetItem(name)
  325. widget.setItem(row, 1, tool_name_item)
  326. dia_item = FCDoubleSpinner()
  327. dia_item.set_precision(self.decimals)
  328. dia_item.setSingleStep(0.1)
  329. dia_item.set_range(0.0, 9999.9999)
  330. dia_item.set_value(float(tooldict['tooldia']))
  331. widget.setCellWidget(row, 2, dia_item)
  332. tool_offset_item = FCComboBox()
  333. for item in self.offset_item_options:
  334. tool_offset_item.addItem(item)
  335. tool_offset_item.set_value(tooldict['offset'])
  336. widget.setCellWidget(row, 3, tool_offset_item)
  337. c_offset_item = FCDoubleSpinner()
  338. c_offset_item.set_precision(self.decimals)
  339. c_offset_item.setSingleStep(0.1)
  340. c_offset_item.set_range(-9999.9999, 9999.9999)
  341. c_offset_item.set_value(float(tooldict['offset_value']))
  342. widget.setCellWidget(row, 4, c_offset_item)
  343. tt_item = FCComboBox()
  344. for item in self.type_item_options:
  345. tt_item.addItem(item)
  346. tt_item.set_value(tooldict['type'])
  347. widget.setCellWidget(row, 5, tt_item)
  348. tshape_item = FCComboBox()
  349. for item in self.tool_type_item_options:
  350. tshape_item.addItem(item)
  351. tshape_item.set_value(tooldict['tool_type'])
  352. widget.setCellWidget(row, 6, tshape_item)
  353. cutz_item = FCDoubleSpinner()
  354. cutz_item.set_precision(self.decimals)
  355. cutz_item.setSingleStep(0.1)
  356. if self.app.defaults['global_machinist_setting']:
  357. cutz_item.set_range(-9999.9999, 9999.9999)
  358. else:
  359. cutz_item.set_range(-9999.9999, -0.0000)
  360. cutz_item.set_value(float(data['cutz']))
  361. widget.setCellWidget(row, 7, cutz_item)
  362. multidepth_item = FCCheckBox()
  363. multidepth_item.set_value(data['multidepth'])
  364. widget.setCellWidget(row, 8, multidepth_item)
  365. # to make the checkbox centered but it can no longer have it's value accessed - needs a fix using findchild()
  366. # multidepth_item = QtWidgets.QWidget()
  367. # cb = FCCheckBox()
  368. # cb.set_value(data['multidepth'])
  369. # qhboxlayout = QtWidgets.QHBoxLayout(multidepth_item)
  370. # qhboxlayout.addWidget(cb)
  371. # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
  372. # qhboxlayout.setContentsMargins(0, 0, 0, 0)
  373. # widget.setCellWidget(row, 8, multidepth_item)
  374. depth_per_pass_item = FCDoubleSpinner()
  375. depth_per_pass_item.set_precision(self.decimals)
  376. depth_per_pass_item.setSingleStep(0.1)
  377. depth_per_pass_item.set_range(0.0, 9999.9999)
  378. depth_per_pass_item.set_value(float(data['depthperpass']))
  379. widget.setCellWidget(row, 9, depth_per_pass_item)
  380. vtip_dia_item = FCDoubleSpinner()
  381. vtip_dia_item.set_precision(self.decimals)
  382. vtip_dia_item.setSingleStep(0.1)
  383. vtip_dia_item.set_range(0.0, 9999.9999)
  384. vtip_dia_item.set_value(float(data['vtipdia']))
  385. widget.setCellWidget(row, 10, vtip_dia_item)
  386. vtip_angle_item = FCDoubleSpinner()
  387. vtip_angle_item.set_precision(self.decimals)
  388. vtip_angle_item.setSingleStep(0.1)
  389. vtip_angle_item.set_range(-360.0, 360.0)
  390. vtip_angle_item.set_value(float(data['vtipangle']))
  391. widget.setCellWidget(row, 11, vtip_angle_item)
  392. travelz_item = FCDoubleSpinner()
  393. travelz_item.set_precision(self.decimals)
  394. travelz_item.setSingleStep(0.1)
  395. if self.app.defaults['global_machinist_setting']:
  396. travelz_item.set_range(-9999.9999, 9999.9999)
  397. else:
  398. travelz_item.set_range(0.0000, 9999.9999)
  399. travelz_item.set_value(float(data['travelz']))
  400. widget.setCellWidget(row, 12, travelz_item)
  401. fr_item = FCDoubleSpinner()
  402. fr_item.set_precision(self.decimals)
  403. fr_item.set_range(0.0, 9999.9999)
  404. fr_item.set_value(float(data['feedrate']))
  405. widget.setCellWidget(row, 13, fr_item)
  406. frz_item = FCDoubleSpinner()
  407. frz_item.set_precision(self.decimals)
  408. frz_item.set_range(0.0, 9999.9999)
  409. frz_item.set_value(float(data['feedrate_z']))
  410. widget.setCellWidget(row, 14, frz_item)
  411. frrapids_item = FCDoubleSpinner()
  412. frrapids_item.set_precision(self.decimals)
  413. frrapids_item.set_range(0.0, 9999.9999)
  414. frrapids_item.set_value(float(data['feedrate_rapid']))
  415. widget.setCellWidget(row, 15, frrapids_item)
  416. spindlespeed_item = FCSpinner()
  417. spindlespeed_item.set_range(0, 1000000)
  418. spindlespeed_item.set_value(int(data['spindlespeed']))
  419. spindlespeed_item.set_step(100)
  420. widget.setCellWidget(row, 16, spindlespeed_item)
  421. dwell_item = FCCheckBox()
  422. dwell_item.set_value(data['dwell'])
  423. widget.setCellWidget(row, 17, dwell_item)
  424. dwelltime_item = FCDoubleSpinner()
  425. dwelltime_item.set_precision(self.decimals)
  426. dwelltime_item.set_range(0.0000, 9999.9999)
  427. dwelltime_item.set_value(float(data['dwelltime']))
  428. widget.setCellWidget(row, 18, dwelltime_item)
  429. pp_item = FCComboBox()
  430. for item in self.app.preprocessors:
  431. pp_item.addItem(item)
  432. pp_item.set_value(data['ppname_g'])
  433. widget.setCellWidget(row, 19, pp_item)
  434. ecut_item = FCCheckBox()
  435. ecut_item.set_value(data['extracut'])
  436. widget.setCellWidget(row, 20, ecut_item)
  437. ecut_length_item = FCDoubleSpinner()
  438. ecut_length_item.set_precision(self.decimals)
  439. ecut_length_item.set_range(0.0000, 9999.9999)
  440. ecut_length_item.set_value(data['extracut_length'])
  441. widget.setCellWidget(row, 21, ecut_length_item)
  442. toolchange_item = FCCheckBox()
  443. toolchange_item.set_value(data['toolchange'])
  444. widget.setCellWidget(row, 22, toolchange_item)
  445. toolchangexy_item = QtWidgets.QTableWidgetItem(str(data['toolchangexy']) if data['toolchangexy'] else '')
  446. widget.setItem(row, 23, toolchangexy_item)
  447. toolchangez_item = FCDoubleSpinner()
  448. toolchangez_item.set_precision(self.decimals)
  449. toolchangez_item.setSingleStep(0.1)
  450. if self.app.defaults['global_machinist_setting']:
  451. toolchangez_item.set_range(-9999.9999, 9999.9999)
  452. else:
  453. toolchangez_item.set_range(0.0000, 9999.9999)
  454. toolchangez_item.set_value(float(data['toolchangez']))
  455. widget.setCellWidget(row, 24, toolchangez_item)
  456. startz_item = QtWidgets.QTableWidgetItem(str(data['startz']) if data['startz'] else '')
  457. widget.setItem(row, 25, startz_item)
  458. endz_item = FCDoubleSpinner()
  459. endz_item.set_precision(self.decimals)
  460. endz_item.setSingleStep(0.1)
  461. if self.app.defaults['global_machinist_setting']:
  462. endz_item.set_range(-9999.9999, 9999.9999)
  463. else:
  464. endz_item.set_range(0.0000, 9999.9999)
  465. endz_item.set_value(float(data['endz']))
  466. widget.setCellWidget(row, 26, endz_item)
  467. def on_tool_add(self):
  468. """
  469. Add a tool in the DB Tool Table
  470. :return: None
  471. """
  472. default_data = {}
  473. default_data.update({
  474. "cutz": float(self.app.defaults["geometry_cutz"]),
  475. "multidepth": self.app.defaults["geometry_multidepth"],
  476. "depthperpass": float(self.app.defaults["geometry_depthperpass"]),
  477. "vtipdia": float(self.app.defaults["geometry_vtipdia"]),
  478. "vtipangle": float(self.app.defaults["geometry_vtipangle"]),
  479. "travelz": float(self.app.defaults["geometry_travelz"]),
  480. "feedrate": float(self.app.defaults["geometry_feedrate"]),
  481. "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]),
  482. "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]),
  483. "spindlespeed": self.app.defaults["geometry_spindlespeed"],
  484. "dwell": self.app.defaults["geometry_dwell"],
  485. "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
  486. "ppname_g": self.app.defaults["geometry_ppname_g"],
  487. "extracut": self.app.defaults["geometry_extracut"],
  488. "extracut_length": float(self.app.defaults["geometry_extracut_length"]),
  489. "toolchange": self.app.defaults["geometry_toolchange"],
  490. "toolchangexy": self.app.defaults["geometry_toolchangexy"],
  491. "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
  492. "startz": self.app.defaults["geometry_startz"],
  493. "endz": float(self.app.defaults["geometry_endz"])
  494. })
  495. dict_elem = {}
  496. dict_elem['name'] = 'new_tool'
  497. if type(self.app.defaults["geometry_cnctooldia"]) == float:
  498. dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
  499. else:
  500. try:
  501. tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
  502. tools_diameters = [eval(a) for a in tools_string if a != '']
  503. dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
  504. except Exception as e:
  505. self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
  506. return
  507. dict_elem['offset'] = 'Path'
  508. dict_elem['offset_value'] = 0.0
  509. dict_elem['type'] = 'Rough'
  510. dict_elem['tool_type'] = 'C1'
  511. dict_elem['data'] = default_data
  512. new_toolid = len(self.db_tool_dict) + 1
  513. self.db_tool_dict[new_toolid] = deepcopy(dict_elem)
  514. # add the new entry to the Tools DB table
  515. self.build_db_ui()
  516. self.callback_on_edited()
  517. self.app.inform.emit('[success] %s' % _("Tool added to DB."))
  518. def on_tool_copy(self):
  519. """
  520. Copy a selection of Tools in the Tools DB table
  521. :return:
  522. """
  523. new_tool_id = self.table_widget.rowCount() + 1
  524. for model_index in self.table_widget.selectionModel().selectedRows():
  525. # index = QtCore.QPersistentModelIndex(model_index)
  526. old_tool_id = self.table_widget.item(model_index.row(), 0).text()
  527. new_tool_id += 1
  528. for toolid, dict_val in list(self.db_tool_dict.items()):
  529. if int(old_tool_id) == int(toolid):
  530. self.db_tool_dict.update({
  531. new_tool_id: deepcopy(dict_val)
  532. })
  533. self.build_db_ui()
  534. self.callback_on_edited()
  535. self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
  536. def on_tool_delete(self):
  537. """
  538. Delete a selection of Tools in the Tools DB table
  539. :return:
  540. """
  541. for model_index in self.table_widget.selectionModel().selectedRows():
  542. # index = QtCore.QPersistentModelIndex(model_index)
  543. toolname_to_remove = self.table_widget.item(model_index.row(), 0).text()
  544. for toolid, dict_val in list(self.db_tool_dict.items()):
  545. if int(toolname_to_remove) == int(toolid):
  546. # remove from the storage
  547. self.db_tool_dict.pop(toolid, None)
  548. self.build_db_ui()
  549. self.callback_on_edited()
  550. self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
  551. def on_export_tools_db_file(self):
  552. self.app.report_usage("on_export_tools_db_file")
  553. self.app.log.debug("on_export_tools_db_file()")
  554. date = str(datetime.today()).rpartition('.')[0]
  555. date = ''.join(c for c in date if c not in ':-')
  556. date = date.replace(' ', '_')
  557. filter__ = "Text File (*.TXT);;All Files (*.*)"
  558. filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Tools Database"),
  559. directory='{l_save}/FlatCAM_{n}_{date}'.format(
  560. l_save=str(self.app.get_last_save_folder()),
  561. n=_("Tools_Database"),
  562. date=date),
  563. filter=filter__)
  564. filename = str(filename)
  565. if filename == "":
  566. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
  567. return
  568. else:
  569. try:
  570. f = open(filename, 'w')
  571. f.close()
  572. except PermissionError:
  573. self.app.inform.emit('[WARNING] %s' %
  574. _("Permission denied, saving not possible.\n"
  575. "Most likely another app is holding the file open and not accessible."))
  576. return
  577. except IOError:
  578. self.app.log.debug('Creating a new Tools DB file ...')
  579. f = open(filename, 'w')
  580. f.close()
  581. except Exception:
  582. e = sys.exc_info()[0]
  583. self.app.log.error("Could not load Tools DB file.")
  584. self.app.log.error(str(e))
  585. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
  586. return
  587. # Save update options
  588. try:
  589. # Save Tools DB in a file
  590. try:
  591. with open(filename, "w") as f:
  592. json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
  593. except Exception as e:
  594. self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
  595. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
  596. return
  597. except Exception:
  598. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
  599. return
  600. self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
  601. def on_import_tools_db_file(self):
  602. self.app.report_usage("on_import_tools_db_file")
  603. self.app.log.debug("on_import_tools_db_file()")
  604. filter__ = "Text File (*.TXT);;All Files (*.*)"
  605. filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
  606. if filename == "":
  607. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
  608. else:
  609. try:
  610. with open(filename) as f:
  611. tools_in_db = f.read()
  612. except IOError:
  613. self.app.log.error("Could not load Tools DB file.")
  614. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
  615. return
  616. try:
  617. self.db_tool_dict = json.loads(tools_in_db)
  618. except Exception:
  619. e = sys.exc_info()[0]
  620. self.app.log.error(str(e))
  621. self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
  622. return
  623. self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
  624. self.build_db_ui()
  625. self.callback_on_edited()
  626. def on_save_tools_db(self, silent=False):
  627. self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.")
  628. filename = self.app.data_path + "/geo_tools_db.FlatDB"
  629. # Preferences save, update the color of the Tools DB Tab text
  630. for idx in range(self.app.ui.plot_tab_area.count()):
  631. if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
  632. self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
  633. # Save Tools DB in a file
  634. try:
  635. f = open(filename, "w")
  636. json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
  637. f.close()
  638. except Exception as e:
  639. self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
  640. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
  641. return
  642. if not silent:
  643. self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
  644. def ui_connect(self):
  645. try:
  646. try:
  647. self.table_widget.itemChanged.disconnect(self.callback_on_edited)
  648. except (TypeError, AttributeError):
  649. pass
  650. self.table_widget.itemChanged.connect(self.callback_on_edited)
  651. except AttributeError:
  652. pass
  653. for row in range(self.table_widget.rowCount()):
  654. for col in range(self.table_widget.columnCount()):
  655. # ComboBox
  656. try:
  657. try:
  658. self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
  659. except (TypeError, AttributeError):
  660. pass
  661. self.table_widget.cellWidget(row, col).currentIndexChanged.connect(self.callback_on_edited)
  662. except AttributeError:
  663. pass
  664. # CheckBox
  665. try:
  666. try:
  667. self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
  668. except (TypeError, AttributeError):
  669. pass
  670. self.table_widget.cellWidget(row, col).toggled.connect(self.callback_on_edited)
  671. except AttributeError:
  672. pass
  673. # SpinBox, DoubleSpinBox
  674. try:
  675. try:
  676. self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
  677. except (TypeError, AttributeError):
  678. pass
  679. self.table_widget.cellWidget(row, col).valueChanged.connect(self.callback_on_edited)
  680. except AttributeError:
  681. pass
  682. def ui_disconnect(self):
  683. try:
  684. self.table_widget.itemChanged.disconnect(self.callback_on_edited)
  685. except (TypeError, AttributeError):
  686. pass
  687. for row in range(self.table_widget.rowCount()):
  688. for col in range(self.table_widget.columnCount()):
  689. # ComboBox
  690. try:
  691. self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
  692. except (TypeError, AttributeError):
  693. pass
  694. # CheckBox
  695. try:
  696. self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
  697. except (TypeError, AttributeError):
  698. pass
  699. # SpinBox, DoubleSpinBox
  700. try:
  701. self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
  702. except (TypeError, AttributeError):
  703. pass
  704. def callback_on_edited(self):
  705. # update the dictionary storage self.db_tool_dict
  706. self.db_tool_dict.clear()
  707. dict_elem = {}
  708. default_data = {}
  709. for row in range(self.table_widget.rowCount()):
  710. new_toolid = row + 1
  711. for col in range(self.table_widget.columnCount()):
  712. column_header_text = self.table_widget.horizontalHeaderItem(col).text()
  713. if column_header_text == _('Tool Name'):
  714. dict_elem['name'] = self.table_widget.item(row, col).text()
  715. elif column_header_text == _('Tool Dia'):
  716. dict_elem['tooldia'] = self.table_widget.cellWidget(row, col).get_value()
  717. elif column_header_text == _('Tool Offset'):
  718. dict_elem['offset'] = self.table_widget.cellWidget(row, col).get_value()
  719. elif column_header_text == _('Custom Offset'):
  720. dict_elem['offset_value'] = self.table_widget.cellWidget(row, col).get_value()
  721. elif column_header_text == _('Tool Type'):
  722. dict_elem['type'] = self.table_widget.cellWidget(row, col).get_value()
  723. elif column_header_text == _('Tool Shape'):
  724. dict_elem['tool_type'] = self.table_widget.cellWidget(row, col).get_value()
  725. else:
  726. if column_header_text == _('Cut Z'):
  727. default_data['cutz'] = self.table_widget.cellWidget(row, col).get_value()
  728. elif column_header_text == _('MultiDepth'):
  729. default_data['multidepth'] = self.table_widget.cellWidget(row, col).get_value()
  730. elif column_header_text == _('DPP'):
  731. default_data['depthperpass'] = self.table_widget.cellWidget(row, col).get_value()
  732. elif column_header_text == _('V-Dia'):
  733. default_data['vtipdia'] = self.table_widget.cellWidget(row, col).get_value()
  734. elif column_header_text == _('V-Angle'):
  735. default_data['vtipangle'] = self.table_widget.cellWidget(row, col).get_value()
  736. elif column_header_text == _('Travel Z'):
  737. default_data['travelz'] = self.table_widget.cellWidget(row, col).get_value()
  738. elif column_header_text == _('FR'):
  739. default_data['feedrate'] = self.table_widget.cellWidget(row, col).get_value()
  740. elif column_header_text == _('FR Z'):
  741. default_data['feedrate_z'] = self.table_widget.cellWidget(row, col).get_value()
  742. elif column_header_text == _('FR Rapids'):
  743. default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value()
  744. elif column_header_text == _('Spindle Speed'):
  745. default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value()
  746. elif column_header_text == _('Dwell'):
  747. default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value()
  748. elif column_header_text == _('Dwelltime'):
  749. default_data['dwelltime'] = self.table_widget.cellWidget(row, col).get_value()
  750. elif column_header_text == _('Preprocessor'):
  751. default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value()
  752. elif column_header_text == _('ExtraCut'):
  753. default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value()
  754. elif column_header_text == _("E-Cut Length"):
  755. default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value()
  756. elif column_header_text == _('Toolchange'):
  757. default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value()
  758. elif column_header_text == _('Toolchange XY'):
  759. default_data['toolchangexy'] = self.table_widget.item(row, col).text()
  760. elif column_header_text == _('Toolchange Z'):
  761. default_data['toolchangez'] = self.table_widget.cellWidget(row, col).get_value()
  762. elif column_header_text == _('Start Z'):
  763. default_data['startz'] = float(self.table_widget.item(row, col).text()) \
  764. if self.table_widget.item(row, col).text() != '' else None
  765. elif column_header_text == _('End Z'):
  766. default_data['endz'] = self.table_widget.cellWidget(row, col).get_value()
  767. dict_elem['data'] = default_data
  768. self.db_tool_dict.update(
  769. {
  770. new_toolid: deepcopy(dict_elem)
  771. }
  772. )
  773. self.callback_app()
  774. def on_tool_requested_from_app(self):
  775. if not self.table_widget.selectionModel().selectedRows():
  776. self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
  777. return
  778. model_index_list = self.table_widget.selectionModel().selectedRows()
  779. for model_index in model_index_list:
  780. selected_row = model_index.row()
  781. tool_uid = selected_row + 1
  782. for key in self.db_tool_dict.keys():
  783. if str(key) == str(tool_uid):
  784. selected_tool = self.db_tool_dict[key]
  785. self.on_tool_request(tool=selected_tool)
  786. def on_cancel_tool(self):
  787. for idx in range(self.app.ui.plot_tab_area.count()):
  788. if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
  789. wdg = self.app.ui.plot_tab_area.widget(idx)
  790. wdg.deleteLater()
  791. self.app.ui.plot_tab_area.removeTab(idx)
  792. self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
  793. # def resize_new_tool_table_widget(self, min_size, max_size):
  794. # """
  795. # Resize the table widget responsible for adding new tool in the Tool Database
  796. #
  797. # :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
  798. # :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
  799. # :return:
  800. # """
  801. # t_height = self.t_height
  802. # if max_size > min_size:
  803. # t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
  804. #
  805. # self.new_tool_table_widget.setMaximumHeight(t_height)
  806. def closeEvent(self, QCloseEvent):
  807. super().closeEvent(QCloseEvent)
  808. class ToolsDB2(QtWidgets.QWidget):
  809. mark_tools_rows = QtCore.pyqtSignal()
  810. def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
  811. super(ToolsDB2, self).__init__(parent)
  812. self.app = app
  813. self.decimals = self.app.decimals
  814. self.callback_app = callback_on_edited
  815. self.on_tool_request = callback_on_tool_request
  816. self.offset_item_options = ["Path", "In", "Out", "Custom"]
  817. self.type_item_options = ["Iso", "Rough", "Finish"]
  818. self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
  819. '''
  820. dict to hold all the tools in the Tools DB
  821. format:
  822. {
  823. tool_id: {
  824. 'name': 'new_tool'
  825. 'tooldia': self.app.defaults["geometry_cnctooldia"]
  826. 'offset': 'Path'
  827. 'offset_value': 0.0
  828. 'type': _('Rough'),
  829. 'tool_type': 'C1'
  830. 'data': dict()
  831. }
  832. }
  833. '''
  834. self.db_tool_dict = {}
  835. # layouts
  836. grid_layout = QtWidgets.QGridLayout()
  837. grid_layout.setColumnStretch(0, 0)
  838. grid_layout.setColumnStretch(1, 1)
  839. self.setLayout(grid_layout)
  840. tree_layout = QtWidgets.QVBoxLayout()
  841. grid_layout.addLayout(tree_layout, 0, 0)
  842. self.tree_widget = FCTree(columns=2, header_hidden=False, protected_column=[0])
  843. self.tree_widget.setHeaderLabels(["ID", "Tool Name"])
  844. self.tree_widget.setIndentation(0)
  845. self.tree_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
  846. self.tree_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
  847. # set alternating colors
  848. # self.tree_widget.setAlternatingRowColors(True)
  849. # p = QtGui.QPalette()
  850. # p.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(226, 237, 253) )
  851. # self.tree_widget.setPalette(p)
  852. self.tree_widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
  853. tree_layout.addWidget(self.tree_widget)
  854. param_hlay = QtWidgets.QHBoxLayout()
  855. param_area = QtWidgets.QScrollArea()
  856. param_widget = QtWidgets.QWidget()
  857. param_widget.setLayout(param_hlay)
  858. param_area.setWidget(param_widget)
  859. param_area.setWidgetResizable(True)
  860. grid_layout.addWidget(param_area, 0, 1)
  861. # ###########################################################################
  862. # ############## The UI form ################################################
  863. # ###########################################################################
  864. self.basic_box = QtWidgets.QGroupBox()
  865. self.basic_box.setStyleSheet("""
  866. QGroupBox
  867. {
  868. font-size: 16px;
  869. font-weight: bold;
  870. }
  871. """)
  872. self.basic_vlay = QtWidgets.QVBoxLayout()
  873. self.basic_box.setTitle(_("Basic Geo Parameters"))
  874. self.basic_box.setFixedWidth(250)
  875. self.advanced_box = QtWidgets.QGroupBox()
  876. self.advanced_box.setStyleSheet("""
  877. QGroupBox
  878. {
  879. font-size: 16px;
  880. font-weight: bold;
  881. }
  882. """)
  883. self.advanced_vlay = QtWidgets.QVBoxLayout()
  884. self.advanced_box.setTitle(_("Advanced Geo Parameters"))
  885. self.advanced_box.setFixedWidth(250)
  886. self.ncc_box = QtWidgets.QGroupBox()
  887. self.ncc_box.setStyleSheet("""
  888. QGroupBox
  889. {
  890. font-size: 16px;
  891. font-weight: bold;
  892. }
  893. """)
  894. self.ncc_vlay = QtWidgets.QVBoxLayout()
  895. self.ncc_box.setTitle(_("NCC Parameters"))
  896. self.ncc_box.setFixedWidth(250)
  897. self.paint_box = QtWidgets.QGroupBox()
  898. self.paint_box.setStyleSheet("""
  899. QGroupBox
  900. {
  901. font-size: 16px;
  902. font-weight: bold;
  903. }
  904. """)
  905. self.paint_vlay = QtWidgets.QVBoxLayout()
  906. self.paint_box.setTitle(_("Paint Parameters"))
  907. self.paint_box.setFixedWidth(250)
  908. self.basic_box.setLayout(self.basic_vlay)
  909. self.advanced_box.setLayout(self.advanced_vlay)
  910. self.ncc_box.setLayout(self.ncc_vlay)
  911. self.paint_box.setLayout(self.paint_vlay)
  912. geo_vlay = QtWidgets.QVBoxLayout()
  913. geo_vlay.addWidget(self.basic_box)
  914. geo_vlay.addWidget(self.advanced_box)
  915. geo_vlay.addStretch()
  916. tools_vlay = QtWidgets.QVBoxLayout()
  917. tools_vlay.addWidget(self.ncc_box)
  918. tools_vlay.addWidget(self.paint_box)
  919. tools_vlay.addStretch()
  920. param_hlay.addLayout(geo_vlay)
  921. param_hlay.addLayout(tools_vlay)
  922. param_hlay.addStretch()
  923. # ###########################################################################
  924. # ############### BASIC UI form #############################################
  925. # ###########################################################################
  926. self.grid0 = QtWidgets.QGridLayout()
  927. self.basic_vlay.addLayout(self.grid0)
  928. self.grid0.setColumnStretch(0, 0)
  929. self.grid0.setColumnStretch(1, 1)
  930. self.basic_vlay.addStretch()
  931. # Tool Name
  932. self.name_label = QtWidgets.QLabel('<span style="color:red;"><b>%s:</b></span>' % _('Tool Name'))
  933. self.name_label.setToolTip(
  934. _("Tool name.\n"
  935. "This is not used in the app, it's function\n"
  936. "is to serve as a note for the user."))
  937. self.name_entry = FCEntry()
  938. self.name_entry.setObjectName('gdb_name')
  939. self.grid0.addWidget(self.name_label, 0, 0)
  940. self.grid0.addWidget(self.name_entry, 0, 1)
  941. # Tool Dia
  942. self.dia_label = QtWidgets.QLabel('%s:' % _('Tool Dia'))
  943. self.dia_label.setToolTip(
  944. _("Tool Diameter."))
  945. self.dia_entry = FCDoubleSpinner()
  946. self.dia_entry.set_range(-9999.9999, 9999.9999)
  947. self.dia_entry.set_precision(self.decimals)
  948. self.dia_entry.setObjectName('gdb_dia')
  949. self.grid0.addWidget(self.dia_label, 1, 0)
  950. self.grid0.addWidget(self.dia_entry, 1, 1)
  951. # Tool Shape
  952. self.shape_label = QtWidgets.QLabel('%s:' % _('Tool Shape'))
  953. self.shape_label.setToolTip(
  954. _("Tool Shape. \n"
  955. "Can be:\n"
  956. "C1 ... C4 = circular tool with x flutes\n"
  957. "B = ball tip milling tool\n"
  958. "V = v-shape milling tool"))
  959. self.shape_combo = FCComboBox()
  960. self.shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"])
  961. self.shape_combo.setObjectName('gdb_shape')
  962. self.grid0.addWidget(self.shape_label, 2, 0)
  963. self.grid0.addWidget(self.shape_combo, 2, 1)
  964. # Cut Z
  965. self.cutz_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
  966. self.cutz_label.setToolTip(
  967. _("Cutting Depth.\n"
  968. "The depth at which to cut into material."))
  969. self.cutz_entry = FCDoubleSpinner()
  970. self.cutz_entry.set_range(-9999.9999, 9999.9999)
  971. self.cutz_entry.set_precision(self.decimals)
  972. self.cutz_entry.setObjectName('gdb_cutz')
  973. self.grid0.addWidget(self.cutz_label, 4, 0)
  974. self.grid0.addWidget(self.cutz_entry, 4, 1)
  975. # Multi Depth
  976. self.multidepth_label = QtWidgets.QLabel('%s:' % _("MultiDepth"))
  977. self.multidepth_label.setToolTip(
  978. _("Multi Depth.\n"
  979. "Selecting this will allow cutting in multiple passes,\n"
  980. "each pass adding a DPP parameter depth."))
  981. self.multidepth_cb = FCCheckBox()
  982. self.multidepth_cb.setObjectName('gdb_multidepth')
  983. self.grid0.addWidget(self.multidepth_label, 5, 0)
  984. self.grid0.addWidget(self.multidepth_cb, 5, 1)
  985. # Depth Per Pass
  986. self.dpp_label = QtWidgets.QLabel('%s:' % _("DPP"))
  987. self.dpp_label.setToolTip(
  988. _("DPP. Depth per Pass.\n"
  989. "The value used to cut into material on each pass."))
  990. self.multidepth_entry = FCDoubleSpinner()
  991. self.multidepth_entry.set_range(-9999.9999, 9999.9999)
  992. self.multidepth_entry.set_precision(self.decimals)
  993. self.multidepth_entry.setObjectName('gdb_multidepth_entry')
  994. self.grid0.addWidget(self.dpp_label, 7, 0)
  995. self.grid0.addWidget(self.multidepth_entry, 7, 1)
  996. # Travel Z
  997. self.travelz_label = QtWidgets.QLabel('%s:' % _("Travel Z"))
  998. self.travelz_label.setToolTip(
  999. _("Clearance Height.\n"
  1000. "Height at which the milling bit will travel between cuts,\n"
  1001. "above the surface of the material, avoiding all fixtures."))
  1002. self.travelz_entry = FCDoubleSpinner()
  1003. self.travelz_entry.set_range(-9999.9999, 9999.9999)
  1004. self.travelz_entry.set_precision(self.decimals)
  1005. self.travelz_entry.setObjectName('gdb_travel')
  1006. self.grid0.addWidget(self.travelz_label, 9, 0)
  1007. self.grid0.addWidget(self.travelz_entry, 9, 1)
  1008. # Feedrate X-Y
  1009. self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y"))
  1010. self.frxy_label.setToolTip(
  1011. _("Feedrate X-Y. Feedrate\n"
  1012. "The speed on XY plane used while cutting into material."))
  1013. self.frxy_entry = FCDoubleSpinner()
  1014. self.frxy_entry.set_range(-999999.9999, 999999.9999)
  1015. self.frxy_entry.set_precision(self.decimals)
  1016. self.frxy_entry.setObjectName('gdb_frxy')
  1017. self.grid0.addWidget(self.frxy_label, 12, 0)
  1018. self.grid0.addWidget(self.frxy_entry, 12, 1)
  1019. # Feedrate Z
  1020. self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z"))
  1021. self.frz_label.setToolTip(
  1022. _("Feedrate Z\n"
  1023. "The speed on Z plane."))
  1024. self.frz_entry = FCDoubleSpinner()
  1025. self.frz_entry.set_range(-999999.9999, 999999.9999)
  1026. self.frz_entry.set_precision(self.decimals)
  1027. self.frz_entry.setObjectName('gdb_frz')
  1028. self.grid0.addWidget(self.frz_label, 14, 0)
  1029. self.grid0.addWidget(self.frz_entry, 14, 1)
  1030. # Spindle Spped
  1031. self.spindle_label = QtWidgets.QLabel('%s:' % _("Spindle Speed"))
  1032. self.spindle_label.setToolTip(
  1033. _("Spindle Speed.\n"
  1034. "If it's left empty it will not be used.\n"
  1035. "The speed of the spindle in RPM."))
  1036. self.spindle_entry = FCDoubleSpinner()
  1037. self.spindle_entry.set_range(-999999.9999, 999999.9999)
  1038. self.spindle_entry.set_precision(self.decimals)
  1039. self.spindle_entry.setObjectName('gdb_spindle')
  1040. self.grid0.addWidget(self.spindle_label, 15, 0)
  1041. self.grid0.addWidget(self.spindle_entry, 15, 1)
  1042. # Dwell
  1043. self.dwell_label = QtWidgets.QLabel('%s:' % _("Dwell"))
  1044. self.dwell_label.setToolTip(
  1045. _("Dwell.\n"
  1046. "Check this if a delay is needed to allow\n"
  1047. "the spindle motor to reach it's set speed."))
  1048. self.dwell_cb = FCCheckBox()
  1049. self.dwell_cb.setObjectName('gdb_dwell')
  1050. self.grid0.addWidget(self.dwell_label, 16, 0)
  1051. self.grid0.addWidget(self.dwell_cb, 16, 1)
  1052. # Dwell Time
  1053. self.dwelltime_label = QtWidgets.QLabel('%s:' % _("Dwelltime"))
  1054. self.dwelltime_label.setToolTip(
  1055. _("Dwell Time.\n"
  1056. "A delay used to allow the motor spindle reach it's set speed."))
  1057. self.dwelltime_entry = FCDoubleSpinner()
  1058. self.dwelltime_entry.set_range(0.0000, 9999.9999)
  1059. self.dwelltime_entry.set_precision(self.decimals)
  1060. self.dwelltime_entry.setObjectName('gdb_dwelltime')
  1061. self.grid0.addWidget(self.dwelltime_label, 17, 0)
  1062. self.grid0.addWidget(self.dwelltime_entry, 17, 1)
  1063. # ###########################################################################
  1064. # ############### ADVANCED UI form ##########################################
  1065. # ###########################################################################
  1066. self.grid1 = QtWidgets.QGridLayout()
  1067. self.advanced_vlay.addLayout(self.grid1)
  1068. self.grid1.setColumnStretch(0, 0)
  1069. self.grid1.setColumnStretch(1, 1)
  1070. self.advanced_vlay.addStretch()
  1071. # Tool Type
  1072. self.type_label = QtWidgets.QLabel('%s:' % _("Tool Type"))
  1073. self.type_label.setToolTip(
  1074. _("Tool Type.\n"
  1075. "Can be:\n"
  1076. "Iso = isolation cut\n"
  1077. "Rough = rough cut, low feedrate, multiple passes\n"
  1078. "Finish = finishing cut, high feedrate"))
  1079. self.type_combo = FCComboBox()
  1080. self.type_combo.addItems(["Iso", "Rough", "Finish"])
  1081. self.type_combo.setObjectName('gdb_type')
  1082. self.grid1.addWidget(self.type_label, 0, 0)
  1083. self.grid1.addWidget(self.type_combo, 0, 1)
  1084. # Tool Offset
  1085. self.tooloffset_label = QtWidgets.QLabel('%s:' % _('Tool Offset'))
  1086. self.tooloffset_label.setToolTip(
  1087. _("Tool Offset.\n"
  1088. "Can be of a few types:\n"
  1089. "Path = zero offset\n"
  1090. "In = offset inside by half of tool diameter\n"
  1091. "Out = offset outside by half of tool diameter\n"
  1092. "Custom = custom offset using the Custom Offset value"))
  1093. self.tooloffset_combo = FCComboBox()
  1094. self.tooloffset_combo.addItems(["Path", "In", "Out", "Custom"])
  1095. self.tooloffset_combo.setObjectName('gdb_tool_offset')
  1096. self.grid1.addWidget(self.tooloffset_label, 2, 0)
  1097. self.grid1.addWidget(self.tooloffset_combo, 2, 1)
  1098. # Custom Offset
  1099. self.custom_offset_label = QtWidgets.QLabel('%s:' % _("Custom Offset"))
  1100. self.custom_offset_label.setToolTip(
  1101. _("Custom Offset.\n"
  1102. "A value to be used as offset from the current path."))
  1103. self.custom_offset_entry = FCDoubleSpinner()
  1104. self.custom_offset_entry.set_range(-9999.9999, 9999.9999)
  1105. self.custom_offset_entry.set_precision(self.decimals)
  1106. self.custom_offset_entry.setObjectName('gdb_custom_offset')
  1107. self.grid1.addWidget(self.custom_offset_label, 5, 0)
  1108. self.grid1.addWidget(self.custom_offset_entry, 5, 1)
  1109. # V-Dia
  1110. self.vdia_label = QtWidgets.QLabel('%s:' % _("V-Dia"))
  1111. self.vdia_label.setToolTip(
  1112. _("V-Dia.\n"
  1113. "Diameter of the tip for V-Shape Tools."))
  1114. self.vdia_entry = FCDoubleSpinner()
  1115. self.vdia_entry.set_range(0.0000, 9999.9999)
  1116. self.vdia_entry.set_precision(self.decimals)
  1117. self.vdia_entry.setObjectName('gdb_vdia')
  1118. self.grid1.addWidget(self.vdia_label, 7, 0)
  1119. self.grid1.addWidget(self.vdia_entry, 7, 1)
  1120. # V-Angle
  1121. self.vangle_label = QtWidgets.QLabel('%s:' % _("V-Angle"))
  1122. self.vangle_label.setToolTip(
  1123. _("V-Agle.\n"
  1124. "Angle at the tip for the V-Shape Tools."))
  1125. self.vangle_entry = FCDoubleSpinner()
  1126. self.vangle_entry.set_range(-360.0, 360.0)
  1127. self.vangle_entry.set_precision(self.decimals)
  1128. self.vangle_entry.setObjectName('gdb_vangle')
  1129. self.grid1.addWidget(self.vangle_label, 8, 0)
  1130. self.grid1.addWidget(self.vangle_entry, 8, 1)
  1131. # Feedrate Rapids
  1132. self.frapids_label = QtWidgets.QLabel('%s:' % _("FR Rapids"))
  1133. self.frapids_label.setToolTip(
  1134. _("FR Rapids. Feedrate Rapids\n"
  1135. "Speed used while moving as fast as possible.\n"
  1136. "This is used only by some devices that can't use\n"
  1137. "the G0 g-code command. Mostly 3D printers."))
  1138. self.frapids_entry = FCDoubleSpinner()
  1139. self.frapids_entry.set_range(0.0000, 9999.9999)
  1140. self.frapids_entry.set_precision(self.decimals)
  1141. self.frapids_entry.setObjectName('gdb_frapids')
  1142. self.grid1.addWidget(self.frapids_label, 10, 0)
  1143. self.grid1.addWidget(self.frapids_entry, 10, 1)
  1144. # Extra Cut
  1145. self.ecut_label = QtWidgets.QLabel('%s:' % _("ExtraCut"))
  1146. self.ecut_label.setToolTip(
  1147. _("Extra Cut.\n"
  1148. "If checked, after a isolation is finished an extra cut\n"
  1149. "will be added where the start and end of isolation meet\n"
  1150. "such as that this point is covered by this extra cut to\n"
  1151. "ensure a complete isolation."))
  1152. self.ecut_cb = FCCheckBox()
  1153. self.ecut_cb.setObjectName('gdb_ecut')
  1154. self.grid1.addWidget(self.ecut_label, 12, 0)
  1155. self.grid1.addWidget(self.ecut_cb, 12, 1)
  1156. # Extra Cut Length
  1157. self.ecut_length_label = QtWidgets.QLabel('%s:' % _("E-Cut Length"))
  1158. self.ecut_length_label.setToolTip(
  1159. _("Extra Cut length.\n"
  1160. "If checked, after a isolation is finished an extra cut\n"
  1161. "will be added where the start and end of isolation meet\n"
  1162. "such as that this point is covered by this extra cut to\n"
  1163. "ensure a complete isolation. This is the length of\n"
  1164. "the extra cut."))
  1165. self.ecut_length_entry = FCDoubleSpinner()
  1166. self.ecut_length_entry.set_range(0.0000, 9999.9999)
  1167. self.ecut_length_entry.set_precision(self.decimals)
  1168. self.ecut_length_entry.setObjectName('gdb_ecut_length')
  1169. self.grid1.addWidget(self.ecut_length_label, 13, 0)
  1170. self.grid1.addWidget(self.ecut_length_entry, 13, 1)
  1171. # ###########################################################################
  1172. # ############### NCC UI form ###############################################
  1173. # ###########################################################################
  1174. self.grid2 = QtWidgets.QGridLayout()
  1175. self.ncc_vlay.addLayout(self.grid2)
  1176. self.grid2.setColumnStretch(0, 0)
  1177. self.grid2.setColumnStretch(1, 1)
  1178. self.ncc_vlay.addStretch()
  1179. # Operation
  1180. op_label = QtWidgets.QLabel('%s:' % _('Operation'))
  1181. op_label.setToolTip(
  1182. _("The 'Operation' can be:\n"
  1183. "- Isolation -> will ensure that the non-copper clearing is always complete.\n"
  1184. "If it's not successful then the non-copper clearing will fail, too.\n"
  1185. "- Clear -> the regular non-copper clearing.")
  1186. )
  1187. self.op_radio = RadioSet([
  1188. {"label": _("Clear"), "value": "clear"},
  1189. {"label": _("Isolation"), "value": "iso"}
  1190. ], orientation='horizontal', stretch=False)
  1191. self.op_radio.setObjectName("gdb_n_operation")
  1192. self.grid2.addWidget(op_label, 13, 0)
  1193. self.grid2.addWidget(self.op_radio, 13, 1)
  1194. # Milling Type Radio Button
  1195. self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
  1196. self.milling_type_label.setToolTip(
  1197. _("Milling type when the selected tool is of type: 'iso_op':\n"
  1198. "- climb / best for precision milling and to reduce tool usage\n"
  1199. "- conventional / useful when there is no backlash compensation")
  1200. )
  1201. self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
  1202. {'label': _('Conventional'), 'value': 'cv'}])
  1203. self.milling_type_radio.setToolTip(
  1204. _("Milling type when the selected tool is of type: 'iso_op':\n"
  1205. "- climb / best for precision milling and to reduce tool usage\n"
  1206. "- conventional / useful when there is no backlash compensation")
  1207. )
  1208. self.milling_type_radio.setObjectName("gdb_n_milling_type")
  1209. self.grid2.addWidget(self.milling_type_label, 14, 0)
  1210. self.grid2.addWidget(self.milling_type_radio, 14, 1)
  1211. # Overlap Entry
  1212. nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
  1213. nccoverlabel.setToolTip(
  1214. _("How much (percentage) of the tool width to overlap each tool pass.\n"
  1215. "Adjust the value starting with lower values\n"
  1216. "and increasing it if areas that should be cleared are still \n"
  1217. "not cleared.\n"
  1218. "Lower values = faster processing, faster execution on CNC.\n"
  1219. "Higher values = slow processing and slow execution on CNC\n"
  1220. "due of too many paths.")
  1221. )
  1222. self.ncc_overlap_entry = FCDoubleSpinner(suffix='%')
  1223. self.ncc_overlap_entry.set_precision(self.decimals)
  1224. self.ncc_overlap_entry.setWrapping(True)
  1225. self.ncc_overlap_entry.setRange(0.000, 99.9999)
  1226. self.ncc_overlap_entry.setSingleStep(0.1)
  1227. self.ncc_overlap_entry.setObjectName("gdb_n_overlap")
  1228. self.grid2.addWidget(nccoverlabel, 15, 0)
  1229. self.grid2.addWidget(self.ncc_overlap_entry, 15, 1)
  1230. # Margin
  1231. nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
  1232. nccmarginlabel.setToolTip(
  1233. _("Bounding box margin.")
  1234. )
  1235. self.ncc_margin_entry = FCDoubleSpinner()
  1236. self.ncc_margin_entry.set_precision(self.decimals)
  1237. self.ncc_margin_entry.set_range(-9999.9999, 9999.9999)
  1238. self.ncc_margin_entry.setObjectName("gdb_n_margin")
  1239. self.grid2.addWidget(nccmarginlabel, 16, 0)
  1240. self.grid2.addWidget(self.ncc_margin_entry, 16, 1)
  1241. # Method
  1242. methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
  1243. methodlabel.setToolTip(
  1244. _("Algorithm for copper clearing:\n"
  1245. "- Standard: Fixed step inwards.\n"
  1246. "- Seed-based: Outwards from seed.\n"
  1247. "- Line-based: Parallel lines.")
  1248. )
  1249. self.ncc_method_combo = FCComboBox()
  1250. self.ncc_method_combo.addItems(
  1251. [_("Standard"), _("Seed"), _("Lines")]
  1252. )
  1253. self.ncc_method_combo.setObjectName("gdb_n_method")
  1254. self.grid2.addWidget(methodlabel, 17, 0)
  1255. self.grid2.addWidget(self.ncc_method_combo, 17, 1)
  1256. # Connect lines
  1257. self.ncc_connect_cb = FCCheckBox('%s' % _("Connect"))
  1258. self.ncc_connect_cb.setObjectName("gdb_n_connect")
  1259. self.ncc_connect_cb.setToolTip(
  1260. _("Draw lines between resulting\n"
  1261. "segments to minimize tool lifts.")
  1262. )
  1263. self.grid2.addWidget(self.ncc_connect_cb, 18, 0)
  1264. # Contour
  1265. self.ncc_contour_cb = FCCheckBox('%s' % _("Contour"))
  1266. self.ncc_contour_cb.setObjectName("gdb_n_contour")
  1267. self.ncc_contour_cb.setToolTip(
  1268. _("Cut around the perimeter of the polygon\n"
  1269. "to trim rough edges.")
  1270. )
  1271. self.grid2.addWidget(self.ncc_contour_cb, 18, 1)
  1272. # ## NCC Offset choice
  1273. self.ncc_choice_offset_cb = FCCheckBox('%s' % _("Offset"))
  1274. self.ncc_choice_offset_cb.setObjectName("gdb_n_offset")
  1275. self.ncc_choice_offset_cb.setToolTip(
  1276. _("If used, it will add an offset to the copper features.\n"
  1277. "The copper clearing will finish to a distance\n"
  1278. "from the copper features.\n"
  1279. "The value can be between 0 and 10 FlatCAM units.")
  1280. )
  1281. self.grid2.addWidget(self.ncc_choice_offset_cb, 19, 0)
  1282. # ## NCC Offset Entry
  1283. self.ncc_offset_spinner = FCDoubleSpinner()
  1284. self.ncc_offset_spinner.set_range(0.00, 10.00)
  1285. self.ncc_offset_spinner.set_precision(4)
  1286. self.ncc_offset_spinner.setWrapping(True)
  1287. self.ncc_offset_spinner.setObjectName("gdb_n_offset_value")
  1288. units = self.app.defaults['units'].upper()
  1289. if units == 'MM':
  1290. self.ncc_offset_spinner.setSingleStep(0.1)
  1291. else:
  1292. self.ncc_offset_spinner.setSingleStep(0.01)
  1293. self.grid2.addWidget(self.ncc_offset_spinner, 19, 1)
  1294. # ###########################################################################
  1295. # ############### Paint UI form #############################################
  1296. # ###########################################################################
  1297. self.grid3 = QtWidgets.QGridLayout()
  1298. self.paint_vlay.addLayout(self.grid3)
  1299. self.grid3.setColumnStretch(0, 0)
  1300. self.grid3.setColumnStretch(1, 1)
  1301. self.paint_vlay.addStretch()
  1302. # Overlap
  1303. ovlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
  1304. ovlabel.setToolTip(
  1305. _("How much (percentage) of the tool width to overlap each tool pass.\n"
  1306. "Adjust the value starting with lower values\n"
  1307. "and increasing it if areas that should be painted are still \n"
  1308. "not painted.\n"
  1309. "Lower values = faster processing, faster execution on CNC.\n"
  1310. "Higher values = slow processing and slow execution on CNC\n"
  1311. "due of too many paths.")
  1312. )
  1313. self.paintoverlap_entry = FCDoubleSpinner(suffix='%')
  1314. self.paintoverlap_entry.set_precision(3)
  1315. self.paintoverlap_entry.setWrapping(True)
  1316. self.paintoverlap_entry.setRange(0.0000, 99.9999)
  1317. self.paintoverlap_entry.setSingleStep(0.1)
  1318. self.paintoverlap_entry.setObjectName('gdb_p_overlap')
  1319. self.grid3.addWidget(ovlabel, 1, 0)
  1320. self.grid3.addWidget(self.paintoverlap_entry, 1, 1)
  1321. # Margin
  1322. marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
  1323. marginlabel.setToolTip(
  1324. _("Distance by which to avoid\n"
  1325. "the edges of the polygon to\n"
  1326. "be painted.")
  1327. )
  1328. self.paintmargin_entry = FCDoubleSpinner()
  1329. self.paintmargin_entry.set_precision(self.decimals)
  1330. self.paintmargin_entry.set_range(-9999.9999, 9999.9999)
  1331. self.paintmargin_entry.setObjectName('gdb_p_margin')
  1332. self.grid3.addWidget(marginlabel, 2, 0)
  1333. self.grid3.addWidget(self.paintmargin_entry, 2, 1)
  1334. # Method
  1335. methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
  1336. methodlabel.setToolTip(
  1337. _("Algorithm for painting:\n"
  1338. "- Standard: Fixed step inwards.\n"
  1339. "- Seed-based: Outwards from seed.\n"
  1340. "- Line-based: Parallel lines.\n"
  1341. "- Laser-lines: Active only for Gerber objects.\n"
  1342. "Will create lines that follow the traces.\n"
  1343. "- Combo: In case of failure a new method will be picked from the above\n"
  1344. "in the order specified.")
  1345. )
  1346. self.paintmethod_combo = FCComboBox()
  1347. self.paintmethod_combo.addItems(
  1348. [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")]
  1349. )
  1350. idx = self.paintmethod_combo.findText(_("Laser_lines"))
  1351. self.paintmethod_combo.model().item(idx).setEnabled(False)
  1352. self.paintmethod_combo.setObjectName('gdb_p_method')
  1353. self.grid3.addWidget(methodlabel, 7, 0)
  1354. self.grid3.addWidget(self.paintmethod_combo, 7, 1)
  1355. # Connect lines
  1356. self.pathconnect_cb = FCCheckBox('%s' % _("Connect"))
  1357. self.pathconnect_cb.setObjectName('gdb_p_connect')
  1358. self.pathconnect_cb.setToolTip(
  1359. _("Draw lines between resulting\n"
  1360. "segments to minimize tool lifts.")
  1361. )
  1362. self.paintcontour_cb = FCCheckBox('%s' % _("Contour"))
  1363. self.paintcontour_cb.setObjectName('gdb_p_contour')
  1364. self.paintcontour_cb.setToolTip(
  1365. _("Cut around the perimeter of the polygon\n"
  1366. "to trim rough edges.")
  1367. )
  1368. self.grid3.addWidget(self.pathconnect_cb, 10, 0)
  1369. self.grid3.addWidget(self.paintcontour_cb, 10, 1)
  1370. # ####################################################################
  1371. # ####################################################################
  1372. # GUI for the lower part of the window
  1373. # ####################################################################
  1374. # ####################################################################
  1375. new_vlay = QtWidgets.QVBoxLayout()
  1376. grid_layout.addLayout(new_vlay, 1, 0, 1, 2)
  1377. self.buttons_frame = QtWidgets.QFrame()
  1378. self.buttons_frame.setContentsMargins(0, 0, 0, 0)
  1379. new_vlay.addWidget(self.buttons_frame)
  1380. self.buttons_box = QtWidgets.QHBoxLayout()
  1381. self.buttons_box.setContentsMargins(0, 0, 0, 0)
  1382. self.buttons_frame.setLayout(self.buttons_box)
  1383. self.buttons_frame.show()
  1384. add_entry_btn = FCButton(_("Add Tool in DB"))
  1385. add_entry_btn.setToolTip(
  1386. _("Add a new tool in the Tools Database.\n"
  1387. "It will be used in the Geometry UI.\n"
  1388. "You can edit it after it is added.")
  1389. )
  1390. self.buttons_box.addWidget(add_entry_btn)
  1391. # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
  1392. # add_fct_entry_btn.setToolTip(
  1393. # _("Add a new tool in the Tools Database.\n"
  1394. # "It will be used in the Paint/NCC Tools UI.\n"
  1395. # "You can edit it after it is added.")
  1396. # )
  1397. # self.buttons_box.addWidget(add_fct_entry_btn)
  1398. remove_entry_btn = FCButton(_("Delete Tool from DB"))
  1399. remove_entry_btn.setToolTip(
  1400. _("Remove a selection of tools in the Tools Database.")
  1401. )
  1402. self.buttons_box.addWidget(remove_entry_btn)
  1403. export_db_btn = FCButton(_("Export DB"))
  1404. export_db_btn.setToolTip(
  1405. _("Save the Tools Database to a custom text file.")
  1406. )
  1407. self.buttons_box.addWidget(export_db_btn)
  1408. import_db_btn = FCButton(_("Import DB"))
  1409. import_db_btn.setToolTip(
  1410. _("Load the Tools Database information's from a custom text file.")
  1411. )
  1412. self.buttons_box.addWidget(import_db_btn)
  1413. self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
  1414. self.add_tool_from_db.setToolTip(
  1415. _("Add a new tool in the Tools Table of the\n"
  1416. "active Geometry object after selecting a tool\n"
  1417. "in the Tools Database.")
  1418. )
  1419. self.add_tool_from_db.hide()
  1420. self.cancel_tool_from_db = FCButton(_("Cancel"))
  1421. self.cancel_tool_from_db.hide()
  1422. hlay = QtWidgets.QHBoxLayout()
  1423. tree_layout.addLayout(hlay)
  1424. hlay.addWidget(self.add_tool_from_db)
  1425. hlay.addWidget(self.cancel_tool_from_db)
  1426. hlay.addStretch()
  1427. # ##############################################################################
  1428. # ##############################################################################
  1429. # ########## SETUP THE DICTIONARIES THAT HOLD THE WIDGETS #####################
  1430. # ##############################################################################
  1431. # ##############################################################################
  1432. self.form_fields = {
  1433. # Basic
  1434. "name": self.name_entry,
  1435. "tooldia": self.dia_entry,
  1436. "tool_type": self.shape_combo,
  1437. "cutz": self.cutz_entry,
  1438. "multidepth": self.multidepth_cb,
  1439. "depthperpass": self.multidepth_entry,
  1440. "travelz": self.travelz_entry,
  1441. "feedrate": self.frxy_entry,
  1442. "feedrate_z": self.frz_entry,
  1443. "spindlespeed": self.spindle_entry,
  1444. "dwell": self.dwell_cb,
  1445. "dwelltime": self.dwelltime_entry,
  1446. # Advanced
  1447. "type": self.type_combo,
  1448. "offset": self.tooloffset_combo,
  1449. "offset_value": self.custom_offset_entry,
  1450. "vtipdia": self.vdia_entry,
  1451. "vtipangle": self.vangle_entry,
  1452. "feedrate_rapid": self.frapids_entry,
  1453. "extracut": self.ecut_cb,
  1454. "extracut_length": self.ecut_length_entry,
  1455. # NCC
  1456. "tools_nccoperation": self.op_radio,
  1457. "tools_nccmilling_type": self.milling_type_radio,
  1458. "tools_nccoverlap": self.ncc_overlap_entry,
  1459. "tools_nccmargin": self.ncc_margin_entry,
  1460. "tools_nccmethod": self.ncc_method_combo,
  1461. "tools_nccconnect": self.ncc_connect_cb,
  1462. "tools_ncccontour": self.ncc_contour_cb,
  1463. "tools_ncc_offset_choice": self.ncc_choice_offset_cb,
  1464. "tools_ncc_offset_value": self.ncc_offset_spinner,
  1465. # Paint
  1466. "tools_paintoverlap": self.paintoverlap_entry,
  1467. "tools_paintmargin": self.paintmargin_entry,
  1468. "tools_paintmethod": self.paintmethod_combo,
  1469. "tools_pathconnect": self.pathconnect_cb,
  1470. "tools_paintcontour": self.paintcontour_cb,
  1471. }
  1472. self.name2option = {
  1473. # Basic
  1474. "gdb_name": "name",
  1475. "gdb_dia": "tooldia",
  1476. "gdb_shape": "tool_type",
  1477. "gdb_cutz": "cutz",
  1478. "gdb_multidepth": "multidepth",
  1479. "gdb_multidepth_entry": "depthperpass",
  1480. "gdb_travel": "travelz",
  1481. "gdb_frxy": "feedrate",
  1482. "gdb_frz": "feedrate_z",
  1483. "gdb_spindle": "spindlespeed",
  1484. "gdb_dwell": "dwell",
  1485. "gdb_dwelltime": "dwelltime",
  1486. # Advanced
  1487. "gdb_type": "type",
  1488. "gdb_tool_offset": "offset",
  1489. "gdb_custom_offset": "offset_value",
  1490. "gdb_vdia": "vtipdia",
  1491. "gdb_vangle": "vtipangle",
  1492. "gdb_frapids": "feedrate_rapid",
  1493. "gdb_ecut": "extracut",
  1494. "gdb_ecut_length": "extracut_length",
  1495. # NCC
  1496. "gdb_n_operation": "tools_nccoperation",
  1497. "gdb_n_overlap": "tools_nccoverlap",
  1498. "gdb_n_margin": "tools_nccmargin",
  1499. "gdb_n_method": "tools_nccmethod",
  1500. "gdb_n_connect": "tools_nccconnect",
  1501. "gdb_n_contour": "tools_ncccontour",
  1502. "gdb_n_offset": "tools_ncc_offset_choice",
  1503. "gdb_n_offset_value": "tools_ncc_offset_value",
  1504. "gdb_n_milling_type": "tools_nccmilling_type",
  1505. # Paint
  1506. 'gdb_p_overlap': "tools_paintoverlap",
  1507. 'gdb_p_margin': "tools_paintmargin",
  1508. 'gdb_p_method': "tools_paintmethod",
  1509. 'gdb_p_connect': "tools_pathconnect",
  1510. 'gdb_p_contour': "tools_paintcontour",
  1511. }
  1512. self.current_toolid = None
  1513. # variable to show if double clicking and item will trigger adding a tool from DB
  1514. self.ok_to_add = False
  1515. # ##############################################################################
  1516. # ######################## SIGNALS #############################################
  1517. # ##############################################################################
  1518. add_entry_btn.clicked.connect(self.on_tool_add)
  1519. remove_entry_btn.clicked.connect(self.on_tool_delete)
  1520. export_db_btn.clicked.connect(self.on_export_tools_db_file)
  1521. import_db_btn.clicked.connect(self.on_import_tools_db_file)
  1522. # closebtn.clicked.connect(self.accept)
  1523. self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
  1524. self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
  1525. # self.tree_widget.selectionModel().selectionChanged.connect(self.on_list_selection_change)
  1526. self.tree_widget.currentItemChanged.connect(self.on_list_selection_change)
  1527. self.tree_widget.itemChanged.connect(self.on_list_item_edited)
  1528. self.tree_widget.customContextMenuRequested.connect(self.on_menu_request)
  1529. self.tree_widget.itemDoubleClicked.connect(self.on_item_double_clicked)
  1530. self.setup_db_ui()
  1531. def on_menu_request(self, pos):
  1532. menu = QtWidgets.QMenu()
  1533. add_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/plus16.png'), _("Add to DB"))
  1534. add_tool.triggered.connect(self.on_tool_add)
  1535. copy_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/copy16.png'), _("Copy from DB"))
  1536. copy_tool.triggered.connect(self.on_tool_copy)
  1537. delete_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete from DB"))
  1538. delete_tool.triggered.connect(self.on_tool_delete)
  1539. # tree_item = self.tree_widget.itemAt(pos)
  1540. menu.exec(self.tree_widget.viewport().mapToGlobal(pos))
  1541. def on_item_double_clicked(self, item, column):
  1542. if column == 0 and self.ok_to_add is True:
  1543. self.ok_to_add = False
  1544. self.on_tool_requested_from_app()
  1545. def on_list_selection_change(self, current, previous):
  1546. # for idx in current.indexes():
  1547. # print(idx.data())
  1548. # print(current.text(0))
  1549. self.current_toolid = int(current.text(0))
  1550. self.storage_to_form(self.db_tool_dict[current.text(0)])
  1551. def on_list_item_edited(self, item, column):
  1552. if column == 0:
  1553. return
  1554. self.name_entry.set_value(item.text(1))
  1555. def storage_to_form(self, dict_storage):
  1556. for form_key in self.form_fields:
  1557. for storage_key in dict_storage:
  1558. if form_key == storage_key:
  1559. try:
  1560. self.form_fields[form_key].set_value(dict_storage[form_key])
  1561. except Exception as e:
  1562. print(str(e))
  1563. if storage_key == 'data':
  1564. for data_key in dict_storage[storage_key]:
  1565. if form_key == data_key:
  1566. try:
  1567. self.form_fields[form_key].set_value(dict_storage['data'][data_key])
  1568. except Exception as e:
  1569. print(str(e))
  1570. def form_to_storage(self, tool):
  1571. self.blockSignals(True)
  1572. widget_changed = self.sender()
  1573. wdg_objname = widget_changed.objectName()
  1574. option_changed = self.name2option[wdg_objname]
  1575. tooluid_item = int(tool)
  1576. for tooluid_key, tooluid_val in self.db_tool_dict.items():
  1577. if int(tooluid_key) == tooluid_item:
  1578. new_option_value = self.form_fields[option_changed].get_value()
  1579. if option_changed in tooluid_val:
  1580. tooluid_val[option_changed] = new_option_value
  1581. if option_changed in tooluid_val['data']:
  1582. tooluid_val['data'][option_changed] = new_option_value
  1583. self.blockSignals(False)
  1584. def setup_db_ui(self):
  1585. filename = self.app.data_path + '/geo_tools_db.FlatDB'
  1586. # load the database tools from the file
  1587. try:
  1588. with open(filename) as f:
  1589. tools = f.read()
  1590. except IOError:
  1591. self.app.log.error("Could not load tools DB file.")
  1592. self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
  1593. return
  1594. try:
  1595. self.db_tool_dict = json.loads(tools)
  1596. except Exception:
  1597. e = sys.exc_info()[0]
  1598. self.app.log.error(str(e))
  1599. self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
  1600. return
  1601. self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
  1602. self.build_db_ui()
  1603. def build_db_ui(self):
  1604. self.ui_disconnect()
  1605. nr_crt = 0
  1606. parent = self.tree_widget
  1607. self.tree_widget.blockSignals(True)
  1608. self.tree_widget.clear()
  1609. self.tree_widget.blockSignals(False)
  1610. for toolid, dict_val in self.db_tool_dict.items():
  1611. row = nr_crt
  1612. nr_crt += 1
  1613. t_name = dict_val['name']
  1614. try:
  1615. # self.add_tool_table_line(row, name=t_name, tooldict=dict_val)
  1616. self.tree_widget.blockSignals(True)
  1617. try:
  1618. self.tree_widget.addParentEditable(parent=parent, title=[str(row+1), t_name], editable=True)
  1619. except Exception as e:
  1620. print('FlatCAMCoomn.ToolDB2.build_db_ui() -> ', str(e))
  1621. self.tree_widget.blockSignals(False)
  1622. except Exception as e:
  1623. self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
  1624. if self.current_toolid is None or self.current_toolid < 1:
  1625. if self.db_tool_dict:
  1626. self.storage_to_form(self.db_tool_dict['1'])
  1627. # Enable GUI
  1628. self.basic_box.setEnabled(True)
  1629. self.advanced_box.setEnabled(True)
  1630. self.ncc_box.setEnabled(True)
  1631. self.paint_box.setEnabled(True)
  1632. self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0))
  1633. # self.tree_widget.setFocus()
  1634. else:
  1635. # Disable GUI
  1636. self.basic_box.setEnabled(False)
  1637. self.advanced_box.setEnabled(False)
  1638. self.ncc_box.setEnabled(False)
  1639. self.paint_box.setEnabled(False)
  1640. else:
  1641. self.storage_to_form(self.db_tool_dict[str(self.current_toolid)])
  1642. self.ui_connect()
  1643. def on_tool_add(self):
  1644. """
  1645. Add a tool in the DB Tool Table
  1646. :return: None
  1647. """
  1648. default_data = {}
  1649. default_data.update({
  1650. "plot": True,
  1651. "cutz": float(self.app.defaults["geometry_cutz"]),
  1652. "multidepth": self.app.defaults["geometry_multidepth"],
  1653. "depthperpass": float(self.app.defaults["geometry_depthperpass"]),
  1654. "vtipdia": float(self.app.defaults["geometry_vtipdia"]),
  1655. "vtipangle": float(self.app.defaults["geometry_vtipangle"]),
  1656. "travelz": float(self.app.defaults["geometry_travelz"]),
  1657. "feedrate": float(self.app.defaults["geometry_feedrate"]),
  1658. "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]),
  1659. "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]),
  1660. "spindlespeed": self.app.defaults["geometry_spindlespeed"],
  1661. "dwell": self.app.defaults["geometry_dwell"],
  1662. "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
  1663. "ppname_g": self.app.defaults["geometry_ppname_g"],
  1664. "extracut": self.app.defaults["geometry_extracut"],
  1665. "extracut_length": float(self.app.defaults["geometry_extracut_length"]),
  1666. "toolchange": self.app.defaults["geometry_toolchange"],
  1667. "toolchangexy": self.app.defaults["geometry_toolchangexy"],
  1668. "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
  1669. "startz": self.app.defaults["geometry_startz"],
  1670. "endz": float(self.app.defaults["geometry_endz"]),
  1671. # NCC
  1672. "tools_nccoperation": self.app.defaults["tools_nccoperation"],
  1673. "tools_nccmilling_type": self.app.defaults["tools_nccmilling_type"],
  1674. "tools_nccoverlap": float(self.app.defaults["tools_nccoverlap"]),
  1675. "tools_nccmargin": float(self.app.defaults["tools_nccmargin"]),
  1676. "tools_nccmethod": self.app.defaults["tools_nccmethod"],
  1677. "tools_nccconnect": self.app.defaults["tools_nccconnect"],
  1678. "tools_ncccontour": self.app.defaults["tools_ncccontour"],
  1679. "tools_ncc_offset_choice": self.app.defaults["tools_ncc_offset_choice"],
  1680. "tools_ncc_offset_value": float(self.app.defaults["tools_ncc_offset_value"]),
  1681. # Paint
  1682. "tools_paintoverlap": float(self.app.defaults["tools_paintoverlap"]),
  1683. "tools_paintmargin": float(self.app.defaults["tools_paintmargin"]),
  1684. "tools_paintmethod": self.app.defaults["tools_paintmethod"],
  1685. "tools_pathconnect": self.app.defaults["tools_pathconnect"],
  1686. "tools_paintcontour": self.app.defaults["tools_paintcontour"],
  1687. })
  1688. dict_elem = {}
  1689. dict_elem['name'] = 'new_tool'
  1690. if type(self.app.defaults["geometry_cnctooldia"]) == float:
  1691. dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
  1692. else:
  1693. try:
  1694. tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
  1695. tools_diameters = [eval(a) for a in tools_string if a != '']
  1696. dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
  1697. except Exception as e:
  1698. self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
  1699. return
  1700. dict_elem['offset'] = 'Path'
  1701. dict_elem['offset_value'] = 0.0
  1702. dict_elem['type'] = 'Rough'
  1703. dict_elem['tool_type'] = 'C1'
  1704. dict_elem['data'] = default_data
  1705. new_toolid = len(self.db_tool_dict) + 1
  1706. self.db_tool_dict[str(new_toolid)] = deepcopy(dict_elem)
  1707. # add the new entry to the Tools DB table
  1708. self.update_storage()
  1709. self.build_db_ui()
  1710. self.app.inform.emit('[success] %s' % _("Tool added to DB."))
  1711. def on_tool_copy(self):
  1712. """
  1713. Copy a selection of Tools in the Tools DB table
  1714. :return:
  1715. """
  1716. new_tool_id = len(self.db_tool_dict)
  1717. for item in self.tree_widget.selectedItems():
  1718. old_tool_id = item.data(0, QtCore.Qt.DisplayRole)
  1719. for toolid, dict_val in list(self.db_tool_dict.items()):
  1720. if int(old_tool_id) == int(toolid):
  1721. new_tool_id += 1
  1722. new_key = str(new_tool_id)
  1723. self.db_tool_dict.update({
  1724. new_key: deepcopy(dict_val)
  1725. })
  1726. self.current_toolid = new_tool_id
  1727. self.update_storage()
  1728. self.build_db_ui()
  1729. self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
  1730. def on_tool_delete(self):
  1731. """
  1732. Delete a selection of Tools in the Tools DB table
  1733. :return:
  1734. """
  1735. for item in self.tree_widget.selectedItems():
  1736. toolname_to_remove = item.data(0, QtCore.Qt.DisplayRole)
  1737. for toolid, dict_val in list(self.db_tool_dict.items()):
  1738. if int(toolname_to_remove) == int(toolid):
  1739. # remove from the storage
  1740. self.db_tool_dict.pop(toolid, None)
  1741. self.current_toolid -= 1
  1742. self.update_storage()
  1743. self.build_db_ui()
  1744. self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
  1745. def on_export_tools_db_file(self):
  1746. self.app.report_usage("on_export_tools_db_file")
  1747. self.app.log.debug("on_export_tools_db_file()")
  1748. date = str(datetime.today()).rpartition('.')[0]
  1749. date = ''.join(c for c in date if c not in ':-')
  1750. date = date.replace(' ', '_')
  1751. filter__ = "Text File (*.TXT);;All Files (*.*)"
  1752. filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Tools Database"),
  1753. directory='{l_save}/FlatCAM_{n}_{date}'.format(
  1754. l_save=str(self.app.get_last_save_folder()),
  1755. n=_("Tools_Database"),
  1756. date=date),
  1757. filter=filter__)
  1758. filename = str(filename)
  1759. if filename == "":
  1760. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
  1761. return
  1762. else:
  1763. try:
  1764. f = open(filename, 'w')
  1765. f.close()
  1766. except PermissionError:
  1767. self.app.inform.emit('[WARNING] %s' %
  1768. _("Permission denied, saving not possible.\n"
  1769. "Most likely another app is holding the file open and not accessible."))
  1770. return
  1771. except IOError:
  1772. self.app.log.debug('Creating a new Tools DB file ...')
  1773. f = open(filename, 'w')
  1774. f.close()
  1775. except Exception:
  1776. e = sys.exc_info()[0]
  1777. self.app.log.error("Could not load Tools DB file.")
  1778. self.app.log.error(str(e))
  1779. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
  1780. return
  1781. # Save update options
  1782. try:
  1783. # Save Tools DB in a file
  1784. try:
  1785. with open(filename, "w") as f:
  1786. json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
  1787. except Exception as e:
  1788. self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
  1789. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
  1790. return
  1791. except Exception:
  1792. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
  1793. return
  1794. self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
  1795. def on_import_tools_db_file(self):
  1796. self.app.report_usage("on_import_tools_db_file")
  1797. self.app.log.debug("on_import_tools_db_file()")
  1798. filter__ = "Text File (*.TXT);;All Files (*.*)"
  1799. filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
  1800. if filename == "":
  1801. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
  1802. else:
  1803. try:
  1804. with open(filename) as f:
  1805. tools_in_db = f.read()
  1806. except IOError:
  1807. self.app.log.error("Could not load Tools DB file.")
  1808. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
  1809. return
  1810. try:
  1811. self.db_tool_dict = json.loads(tools_in_db)
  1812. except Exception:
  1813. e = sys.exc_info()[0]
  1814. self.app.log.error(str(e))
  1815. self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
  1816. return
  1817. self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
  1818. self.build_db_ui()
  1819. self.update_storage()
  1820. def on_save_tools_db(self, silent=False):
  1821. self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.")
  1822. filename = self.app.data_path + "/geo_tools_db.FlatDB"
  1823. # Preferences save, update the color of the Tools DB Tab text
  1824. for idx in range(self.app.ui.plot_tab_area.count()):
  1825. if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
  1826. self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
  1827. # Save Tools DB in a file
  1828. try:
  1829. f = open(filename, "w")
  1830. json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
  1831. f.close()
  1832. except Exception as e:
  1833. self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
  1834. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
  1835. return
  1836. if not silent:
  1837. self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
  1838. def ui_connect(self):
  1839. # make sure that we don't make multiple connections to the widgets
  1840. self.ui_disconnect()
  1841. self.name_entry.editingFinished.connect(self.update_tree_name)
  1842. for key in self.form_fields:
  1843. wdg = self.form_fields[key]
  1844. # FCEntry
  1845. if isinstance(wdg, FCEntry):
  1846. wdg.textChanged.connect(self.update_storage)
  1847. # ComboBox
  1848. if isinstance(wdg, FCComboBox):
  1849. wdg.currentIndexChanged.connect(self.update_storage)
  1850. # CheckBox
  1851. if isinstance(wdg, FCCheckBox):
  1852. wdg.toggled.connect(self.update_storage)
  1853. # FCRadio
  1854. if isinstance(wdg, RadioSet):
  1855. wdg.activated_custom.connect(self.update_storage)
  1856. # SpinBox, DoubleSpinBox
  1857. if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
  1858. wdg.valueChanged.connect(self.update_storage)
  1859. def ui_disconnect(self):
  1860. try:
  1861. self.name_entry.editingFinished.disconnect(self.update_tree_name)
  1862. except (TypeError, AttributeError):
  1863. pass
  1864. for key in self.form_fields:
  1865. wdg = self.form_fields[key]
  1866. # FCEntry
  1867. if isinstance(wdg, FCEntry):
  1868. try:
  1869. wdg.textChanged.disconnect(self.update_storage)
  1870. except (TypeError, AttributeError):
  1871. pass
  1872. # ComboBox
  1873. if isinstance(wdg, FCComboBox):
  1874. try:
  1875. wdg.currentIndexChanged.disconnect(self.update_storage)
  1876. except (TypeError, AttributeError):
  1877. pass
  1878. # CheckBox
  1879. if isinstance(wdg, FCCheckBox):
  1880. try:
  1881. wdg.toggled.disconnect(self.update_storage)
  1882. except (TypeError, AttributeError):
  1883. pass
  1884. # FCRadio
  1885. if isinstance(wdg, RadioSet):
  1886. try:
  1887. wdg.activated_custom.disconnect(self.update_storage)
  1888. except (TypeError, AttributeError):
  1889. pass
  1890. # SpinBox, DoubleSpinBox
  1891. if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
  1892. try:
  1893. wdg.valueChanged.disconnect(self.update_storage)
  1894. except (TypeError, AttributeError):
  1895. pass
  1896. def update_tree_name(self):
  1897. val = self.name_entry.get_value()
  1898. item = self.tree_widget.currentItem()
  1899. # I'm setting the value for the second column (designated by 1) because first column holds the ID
  1900. # and second column holds the Name (this behavior is set in the build_ui method)
  1901. item.setData(1, QtCore.Qt.DisplayRole, val)
  1902. def update_storage(self):
  1903. """
  1904. Update the dictionary that is the storage of the tools 'database'
  1905. :return:
  1906. """
  1907. tool_id = str(self.current_toolid)
  1908. try:
  1909. wdg = self.sender()
  1910. assert isinstance(wdg, QtWidgets.QWidget), "Expected a QWidget got %s" % type(wdg)
  1911. if wdg is None:
  1912. return
  1913. wdg_name = wdg.objectName()
  1914. val = wdg.get_value()
  1915. except AttributeError:
  1916. return
  1917. if wdg_name == "gdb_name":
  1918. self.db_tool_dict[tool_id]['name'] = val
  1919. elif wdg_name == "gdb_dia":
  1920. self.db_tool_dict[tool_id]['tooldia'] = val
  1921. elif wdg_name == "gdb_tool_offset":
  1922. self.db_tool_dict[tool_id]['offset'] = val
  1923. elif wdg_name == "gdb_custom_offset":
  1924. self.db_tool_dict[tool_id]['offset_value'] = val
  1925. elif wdg_name == "gdb_type":
  1926. self.db_tool_dict[tool_id]['type'] = val
  1927. elif wdg_name == "gdb_shape":
  1928. self.db_tool_dict[tool_id]['tool_type'] = val
  1929. else:
  1930. if wdg_name == "gdb_cutz":
  1931. self.db_tool_dict[tool_id]['data']['cutz'] = val
  1932. elif wdg_name == "gdb_multidepth":
  1933. self.db_tool_dict[tool_id]['data']['multidepth'] = val
  1934. elif wdg_name == "gdb_multidepth_entry":
  1935. self.db_tool_dict[tool_id]['data']['depthperpass'] = val
  1936. elif wdg_name == "gdb_travel":
  1937. self.db_tool_dict[tool_id]['data']['travelz'] = val
  1938. elif wdg_name == "gdb_frxy":
  1939. self.db_tool_dict[tool_id]['data']['feedrate'] = val
  1940. elif wdg_name == "gdb_frz":
  1941. self.db_tool_dict[tool_id]['data']['feedrate_z'] = val
  1942. elif wdg_name == "gdb_spindle":
  1943. self.db_tool_dict[tool_id]['data']['spindlespeed'] = val
  1944. elif wdg_name == "gdb_dwell":
  1945. self.db_tool_dict[tool_id]['data']['dwell'] = val
  1946. elif wdg_name == "gdb_dwelltime":
  1947. self.db_tool_dict[tool_id]['data']['dwelltime'] = val
  1948. elif wdg_name == "gdb_vdia":
  1949. self.db_tool_dict[tool_id]['data']['vtipdia'] = val
  1950. elif wdg_name == "gdb_vangle":
  1951. self.db_tool_dict[tool_id]['data']['vtipangle'] = val
  1952. elif wdg_name == "gdb_frapids":
  1953. self.db_tool_dict[tool_id]['data']['feedrate_rapid'] = val
  1954. elif wdg_name == "gdb_ecut":
  1955. self.db_tool_dict[tool_id]['data']['extracut'] = val
  1956. elif wdg_name == "gdb_ecut_length":
  1957. self.db_tool_dict[tool_id]['data']['extracut_length'] = val
  1958. # NCC Tool
  1959. elif wdg_name == "gdb_n_operation":
  1960. self.db_tool_dict[tool_id]['data']['tools_nccoperation'] = val
  1961. elif wdg_name == "gdb_n_overlap":
  1962. self.db_tool_dict[tool_id]['data']['tools_nccoverlap'] = val
  1963. elif wdg_name == "gdb_n_margin":
  1964. self.db_tool_dict[tool_id]['data']['tools_nccmargin'] = val
  1965. elif wdg_name == "gdb_n_method":
  1966. self.db_tool_dict[tool_id]['data']['tools_nccmethod'] = val
  1967. elif wdg_name == "gdb_n_connect":
  1968. self.db_tool_dict[tool_id]['data']['tools_nccconnect'] = val
  1969. elif wdg_name == "gdb_n_contour":
  1970. self.db_tool_dict[tool_id]['data']['tools_ncccontour'] = val
  1971. elif wdg_name == "gdb_n_offset":
  1972. self.db_tool_dict[tool_id]['data']['tools_ncc_offset_choice'] = val
  1973. elif wdg_name == "gdb_n_offset_value":
  1974. self.db_tool_dict[tool_id]['data']['tools_ncc_offset_value'] = val
  1975. elif wdg_name == "gdb_n_milling_type":
  1976. self.db_tool_dict[tool_id]['data']['tools_nccmilling_type'] = val
  1977. # Paint Tool
  1978. elif wdg_name == "gdb_p_overlap":
  1979. self.db_tool_dict[tool_id]['data']['tools_paintoverlap'] = val
  1980. elif wdg_name == "gdb_p_margin":
  1981. self.db_tool_dict[tool_id]['data']['tools_paintmargin'] = val
  1982. elif wdg_name == "gdb_p_method":
  1983. self.db_tool_dict[tool_id]['data']['tools_paintmethod'] = val
  1984. elif wdg_name == "gdb_p_connect":
  1985. self.db_tool_dict[tool_id]['data']['tools_pathconnect'] = val
  1986. elif wdg_name == "gdb_p_contour":
  1987. self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val
  1988. self.callback_app()
  1989. def on_tool_requested_from_app(self):
  1990. if not self.tree_widget.selectedItems():
  1991. self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
  1992. return
  1993. for item in self.tree_widget.selectedItems():
  1994. tool_uid = item.data(0, QtCore.Qt.DisplayRole)
  1995. for key in self.db_tool_dict.keys():
  1996. if str(key) == str(tool_uid):
  1997. selected_tool = self.db_tool_dict[key]
  1998. self.on_tool_request(tool=selected_tool)
  1999. def on_cancel_tool(self):
  2000. for idx in range(self.app.ui.plot_tab_area.count()):
  2001. if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
  2002. wdg = self.app.ui.plot_tab_area.widget(idx)
  2003. wdg.deleteLater()
  2004. self.app.ui.plot_tab_area.removeTab(idx)
  2005. self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
  2006. # def resize_new_tool_table_widget(self, min_size, max_size):
  2007. # """
  2008. # Resize the table widget responsible for adding new tool in the Tool Database
  2009. #
  2010. # :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
  2011. # :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
  2012. # :return:
  2013. # """
  2014. # t_height = self.t_height
  2015. # if max_size > min_size:
  2016. # t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
  2017. #
  2018. # self.new_tool_table_widget.setMaximumHeight(t_height)
  2019. def closeEvent(self, QCloseEvent):
  2020. super().closeEvent(QCloseEvent)