FlatCAMCommon.py 100 KB

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