Просмотр исходного кода

- replaced the testing if instance of FlatCAMObj with testing the obj.kind attribute
- removed the import of the whole FlatCAMApp file only for the usage of GracefulException
- remove the import of FlatCAMApp and used alternate ways
- optimized the imports in some files
- moved the Bookmarksmanager and ToolDB classes into their own files
- solved some bugs that were not so visible in the Editors and HPGL parser

Marius Stanciu 5 лет назад
Родитель
Сommit
3ec666edbb

+ 6 - 0
CHANGELOG.md

@@ -13,6 +13,12 @@ CHANGELOG for FlatCAM beta
 - updated the requirements.txt file to request that the Shapely package needs to be at least version 1.7.0 as it is needed in the latest versions of FlatCAM beta
 - updated the requirements.txt file to request that the Shapely package needs to be at least version 1.7.0 as it is needed in the latest versions of FlatCAM beta
 - some TOOD cleanups
 - some TOOD cleanups
 - minor changes
 - minor changes
+- replaced the testing if instance of FlatCAMObj with testing the obj.kind attribute
+- removed the import of the whole FlatCAMApp file only for the usage of GracefulException
+- remove the import of FlatCAMApp and used alternate ways
+- optimized the imports in some files
+- moved the Bookmarksmanager and ToolDB classes into their own files
+- solved some bugs that were not so visible in the Editors and HPGL parser
 
 
 25.04.2020
 25.04.2020
 
 

+ 5 - 11
FlatCAMApp.py

@@ -46,6 +46,10 @@ import socket
 # #######################################
 # #######################################
 # #      Imports part of FlatCAM       ##
 # #      Imports part of FlatCAM       ##
 # #######################################
 # #######################################
+from FlatCAMCommon import LoudDict, color_variant
+from FlatCAMBookmark import BookmarkManager
+from FlatCAMDB import ToolsDB2
+
 from ObjectCollection import *
 from ObjectCollection import *
 from FlatCAMObj import *
 from FlatCAMObj import *
 from camlib import to_dict, dict2obj, ET, ParseError
 from camlib import to_dict, dict2obj, ET, ParseError
@@ -55,7 +59,6 @@ from flatcamGUI.PlotCanvasLegacy import *
 from flatcamGUI.FlatCAMGUI import *
 from flatcamGUI.FlatCAMGUI import *
 from flatcamGUI.GUIElements import FCFileSaveDialog
 from flatcamGUI.GUIElements import FCFileSaveDialog
 
 
-from FlatCAMCommon import LoudDict, BookmarkManager, ToolsDB, ToolsDB2, color_variant
 from FlatCAMPostProc import load_preprocessors
 from FlatCAMPostProc import load_preprocessors
 
 
 from flatcamEditors.FlatCAMGeoEditor import FlatCAMGeoEditor
 from flatcamEditors.FlatCAMGeoEditor import FlatCAMGeoEditor
@@ -4313,7 +4316,7 @@ class App(QtCore.QObject):
         # update the KeyWords list with the name of the file
         # update the KeyWords list with the name of the file
         self.myKeywords.append(obj.options['name'])
         self.myKeywords.append(obj.options['name'])
 
 
-        FlatCAMApp.App.log.debug("Moving new object back to main thread.")
+        log.debug("Moving new object back to main thread.")
 
 
         # Move the object to the main thread and let the app know that it is available.
         # Move the object to the main thread and let the app know that it is available.
         obj.moveToThread(self.main_thread)
         obj.moveToThread(self.main_thread)
@@ -12862,13 +12865,4 @@ class ArgsThread(QtCore.QObject):
     def run(self):
     def run(self):
         self.my_loop(self.address)
         self.my_loop(self.address)
 
 
-
-class GracefulException(Exception):
-    # Graceful Exception raised when the user is requesting to cancel the current threaded task
-    def __init__(self):
-        super().__init__()
-
-    def __str__(self):
-        return '\n\n%s' % _("The user requested a graceful exit of the current task.")
-
 # end of file
 # end of file

+ 381 - 0
FlatCAMBookmark.py

@@ -0,0 +1,381 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCFileSaveDialog
+
+import sys
+import webbrowser
+
+from copy import deepcopy
+from datetime import datetime
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class BookmarkManager(QtWidgets.QWidget):
+
+    mark_rows = QtCore.pyqtSignal()
+
+    def __init__(self, app, storage, parent=None):
+        super(BookmarkManager, self).__init__(parent)
+
+        self.app = app
+
+        assert isinstance(storage, dict), "Storage argument is not a dictionary"
+
+        self.bm_dict = deepcopy(storage)
+
+        # Icon and title
+        # self.setWindowIcon(parent.app_icon)
+        # self.setWindowTitle(_("Bookmark Manager"))
+        # self.resize(600, 400)
+
+        # title = QtWidgets.QLabel(
+        #     "<font size=8><B>FlatCAM</B></font><BR>"
+        # )
+        # title.setOpenExternalLinks(True)
+
+        # layouts
+        layout = QtWidgets.QVBoxLayout()
+        self.setLayout(layout)
+
+        table_hlay = QtWidgets.QHBoxLayout()
+        layout.addLayout(table_hlay)
+
+        self.table_widget = FCTable(drag_drop=True, protected_rows=[0, 1])
+        self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+        table_hlay.addWidget(self.table_widget)
+
+        self.table_widget.setColumnCount(3)
+        self.table_widget.setColumnWidth(0, 20)
+        self.table_widget.setHorizontalHeaderLabels(
+            [
+                '#',
+                _('Title'),
+                _('Web Link')
+            ]
+        )
+        self.table_widget.horizontalHeaderItem(0).setToolTip(
+            _("Index.\n"
+              "The rows in gray color will populate the Bookmarks menu.\n"
+              "The number of gray colored rows is set in Preferences."))
+        self.table_widget.horizontalHeaderItem(1).setToolTip(
+            _("Description of the link that is set as an menu action.\n"
+              "Try to keep it short because it is installed as a menu item."))
+        self.table_widget.horizontalHeaderItem(2).setToolTip(
+            _("Web Link. E.g: https://your_website.org "))
+
+        # pal = QtGui.QPalette()
+        # pal.setColor(QtGui.QPalette.Background, Qt.white)
+
+        # New Bookmark
+        new_vlay = QtWidgets.QVBoxLayout()
+        layout.addLayout(new_vlay)
+
+        new_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Bookmark"))
+        new_vlay.addWidget(new_title_lbl)
+
+        form0 = QtWidgets.QFormLayout()
+        new_vlay.addLayout(form0)
+
+        title_lbl = QtWidgets.QLabel('%s:' % _("Title"))
+        self.title_entry = FCEntry()
+        form0.addRow(title_lbl, self.title_entry)
+
+        link_lbl = QtWidgets.QLabel('%s:' % _("Web Link"))
+        self.link_entry = FCEntry()
+        self.link_entry.set_value('http://')
+        form0.addRow(link_lbl, self.link_entry)
+
+        # Buttons Layout
+        button_hlay = QtWidgets.QHBoxLayout()
+        layout.addLayout(button_hlay)
+
+        add_entry_btn = FCButton(_("Add Entry"))
+        remove_entry_btn = FCButton(_("Remove Entry"))
+        export_list_btn = FCButton(_("Export List"))
+        import_list_btn = FCButton(_("Import List"))
+        # closebtn = QtWidgets.QPushButton(_("Close"))
+
+        # button_hlay.addStretch()
+        button_hlay.addWidget(add_entry_btn)
+        button_hlay.addWidget(remove_entry_btn)
+
+        button_hlay.addWidget(export_list_btn)
+        button_hlay.addWidget(import_list_btn)
+        # button_hlay.addWidget(closebtn)
+        # ##############################################################################
+        # ######################## SIGNALS #############################################
+        # ##############################################################################
+
+        add_entry_btn.clicked.connect(self.on_add_entry)
+        remove_entry_btn.clicked.connect(self.on_remove_entry)
+        export_list_btn.clicked.connect(self.on_export_bookmarks)
+        import_list_btn.clicked.connect(self.on_import_bookmarks)
+        self.title_entry.returnPressed.connect(self.on_add_entry)
+        self.link_entry.returnPressed.connect(self.on_add_entry)
+        # closebtn.clicked.connect(self.accept)
+
+        self.table_widget.drag_drop_sig.connect(self.mark_table_rows_for_actions)
+        self.build_bm_ui()
+
+    def build_bm_ui(self):
+
+        self.table_widget.setRowCount(len(self.bm_dict))
+
+        nr_crt = 0
+        sorted_bookmarks = sorted(list(self.bm_dict.items()), key=lambda x: int(x[0]))
+        for entry, bookmark in sorted_bookmarks:
+            row = nr_crt
+            nr_crt += 1
+
+            title = bookmark[0]
+            weblink = bookmark[1]
+
+            id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt))
+            # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+            self.table_widget.setItem(row, 0, id_item)  # Tool name/id
+
+            title_item = QtWidgets.QTableWidgetItem(title)
+            self.table_widget.setItem(row, 1, title_item)
+
+            weblink_txt = QtWidgets.QTextBrowser()
+            weblink_txt.setOpenExternalLinks(True)
+            weblink_txt.setFrameStyle(QtWidgets.QFrame.NoFrame)
+            weblink_txt.document().setDefaultStyleSheet("a{ text-decoration: none; }")
+
+            weblink_txt.setHtml('<a href=%s>%s</a>' % (weblink, weblink))
+
+            self.table_widget.setCellWidget(row, 2, weblink_txt)
+
+            vertical_header = self.table_widget.verticalHeader()
+            vertical_header.hide()
+
+            horizontal_header = self.table_widget.horizontalHeader()
+            horizontal_header.setMinimumSectionSize(10)
+            horizontal_header.setDefaultSectionSize(70)
+            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+            horizontal_header.resizeSection(0, 20)
+            horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
+            horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
+
+        self.mark_table_rows_for_actions()
+
+        self.app.defaults["global_bookmarks"].clear()
+        for key, val in self.bm_dict.items():
+            self.app.defaults["global_bookmarks"][key] = deepcopy(val)
+
+    def on_add_entry(self, **kwargs):
+        """
+        Add a entry in the Bookmark Table and in the menu actions
+        :return: None
+        """
+        if 'title' in kwargs:
+            title = kwargs['title']
+        else:
+            title = self.title_entry.get_value()
+        if title == '':
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Title entry is empty."))
+            return 'fail'
+
+        if 'link' in kwargs:
+            link = kwargs['link']
+        else:
+            link = self.link_entry.get_value()
+
+        if link == 'http://':
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Web link entry is empty."))
+            return 'fail'
+
+        # if 'http' not in link or 'https' not in link:
+        #     link = 'http://' + link
+
+        for bookmark in self.bm_dict.values():
+            if title == bookmark[0] or link == bookmark[1]:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Either the Title or the Weblink already in the table."))
+                return 'fail'
+
+        # for some reason if the last char in the weblink is a slash it does not make the link clickable
+        # so I remove it
+        if link[-1] == '/':
+            link = link[:-1]
+        # add the new entry to storage
+        new_entry = len(self.bm_dict) + 1
+        self.bm_dict[str(new_entry)] = [title, link]
+
+        # add the link to the menu but only if it is within the set limit
+        bm_limit = int(self.app.defaults["global_bookmarks_limit"])
+        if len(self.bm_dict) < bm_limit:
+            act = QtWidgets.QAction(parent=self.app.ui.menuhelp_bookmarks)
+            act.setText(title)
+            act.setIcon(QtGui.QIcon(self.app.resource_location + '/link16.png'))
+            act.triggered.connect(lambda: webbrowser.open(link))
+            self.app.ui.menuhelp_bookmarks.insertAction(self.app.ui.menuhelp_bookmarks_manager, act)
+
+        self.app.inform.emit('[success] %s' % _("Bookmark added."))
+
+        # add the new entry to the bookmark manager table
+        self.build_bm_ui()
+
+    def on_remove_entry(self):
+        """
+        Remove an Entry in the Bookmark table and from the menu actions
+        :return:
+        """
+        index_list = []
+        for model_index in self.table_widget.selectionModel().selectedRows():
+            index = QtCore.QPersistentModelIndex(model_index)
+            index_list.append(index)
+            title_to_remove = self.table_widget.item(model_index.row(), 1).text()
+
+            if title_to_remove == 'FlatCAM' or title_to_remove == 'Backup Site':
+                self.app.inform.emit('[WARNING_NOTCL] %s.' % _("This bookmark can not be removed"))
+                self.build_bm_ui()
+                return
+            else:
+                for k, bookmark in list(self.bm_dict.items()):
+                    if title_to_remove == bookmark[0]:
+                        # remove from the storage
+                        self.bm_dict.pop(k, None)
+
+                        for act in self.app.ui.menuhelp_bookmarks.actions():
+                            if act.text() == title_to_remove:
+                                # disconnect the signal
+                                try:
+                                    act.triggered.disconnect()
+                                except TypeError:
+                                    pass
+                                # remove the action from the menu
+                                self.app.ui.menuhelp_bookmarks.removeAction(act)
+
+        # house keeping: it pays to have keys increased by one
+        new_key = 0
+        new_dict = {}
+        for k, v in self.bm_dict.items():
+            # we start with key 1 so we can use the len(self.bm_dict)
+            # when adding bookmarks (keys in bm_dict)
+            new_key += 1
+            new_dict[str(new_key)] = v
+
+        self.bm_dict = deepcopy(new_dict)
+        new_dict.clear()
+
+        self.app.inform.emit('[success] %s' % _("Bookmark removed."))
+
+        # for index in index_list:
+        #     self.table_widget.model().removeRow(index.row())
+        self.build_bm_ui()
+
+    def on_export_bookmarks(self):
+        self.app.report_usage("on_export_bookmarks")
+        self.app.log.debug("on_export_bookmarks()")
+
+        date = str(datetime.today()).rpartition('.')[0]
+        date = ''.join(c for c in date if c not in ':-')
+        date = date.replace(' ', '_')
+
+        filter__ = "Text File (*.TXT);;All Files (*.*)"
+        filename, _f = FCFileSaveDialog.get_saved_filename( caption=_("Export FlatCAM Bookmarks"),
+                                                             directory='{l_save}/FlatCAM_{n}_{date}'.format(
+                                                                 l_save=str(self.app.get_last_save_folder()),
+                                                                 n=_("Bookmarks"),
+                                                                 date=date),
+                                                             filter=filter__)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+            return
+        else:
+            try:
+                f = open(filename, 'w')
+                f.close()
+            except PermissionError:
+                self.app.inform.emit('[WARNING] %s' %
+                                     _("Permission denied, saving not possible.\n"
+                                       "Most likely another app is holding the file open and not accessible."))
+                return
+            except IOError:
+                self.app.log.debug('Creating a new bookmarks file ...')
+                f = open(filename, 'w')
+                f.close()
+            except Exception:
+                e = sys.exc_info()[0]
+                self.app.log.error("Could not load defaults file.")
+                self.app.log.error(str(e))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file."))
+                return
+
+            # Save Bookmarks to a file
+            try:
+                with open(filename, "w") as f:
+                    for title, link in self.bm_dict.items():
+                        line2write = str(title) + ':' + str(link) + '\n'
+                        f.write(line2write)
+            except Exception:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write bookmarks to file."))
+                return
+        self.app.inform.emit('[success] %s: %s' % (_("Exported bookmarks to"), filename))
+
+    def on_import_bookmarks(self):
+        self.app.log.debug("on_import_bookmarks()")
+
+        filter_ = "Text File (*.txt);;All Files (*.*)"
+        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Bookmarks"), filter=filter_)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+        else:
+            try:
+                with open(filename) as f:
+                    bookmarks = f.readlines()
+            except IOError:
+                self.app.log.error("Could not load bookmarks file.")
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file."))
+                return
+
+            for line in bookmarks:
+                proc_line = line.replace(' ', '').partition(':')
+                self.on_add_entry(title=proc_line[0], link=proc_line[2])
+
+            self.app.inform.emit('[success] %s: %s' % (_("Imported Bookmarks from"), filename))
+
+    def mark_table_rows_for_actions(self):
+        for row in range(self.table_widget.rowCount()):
+            item_to_paint = self.table_widget.item(row, 0)
+            if row < self.app.defaults["global_bookmarks_limit"]:
+                item_to_paint.setBackground(QtGui.QColor('gray'))
+                # item_to_paint.setForeground(QtGui.QColor('black'))
+            else:
+                item_to_paint.setBackground(QtGui.QColor('white'))
+                # item_to_paint.setForeground(QtGui.QColor('black'))
+
+    def rebuild_actions(self):
+        # rebuild the storage to reflect the order of the lines
+        self.bm_dict.clear()
+        for row in range(self.table_widget.rowCount()):
+            title = self.table_widget.item(row, 1).text()
+            wlink = self.table_widget.cellWidget(row, 2).toPlainText()
+
+            entry = int(row) + 1
+            self.bm_dict.update(
+                {
+                    str(entry): [title, wlink]
+                }
+            )
+
+        self.app.install_bookmarks(book_dict=self.bm_dict)
+
+    # def accept(self):
+    #     self.rebuild_actions()
+    #     super().accept()
+
+    def closeEvent(self, QCloseEvent):
+        self.rebuild_actions()
+        super().closeEvent(QCloseEvent)

+ 9 - 2757
FlatCAMCommon.py

@@ -11,17 +11,6 @@
 # Date: 11/4/2019                                          #
 # Date: 11/4/2019                                          #
 # ##########################################################
 # ##########################################################
 
 
-from PyQt5 import QtGui, QtCore, QtWidgets
-from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
-    FCTree, RadioSet, FCFileSaveDialog
-from camlib import to_dict
-
-import sys
-import webbrowser
-import json
-
-from copy import deepcopy
-from datetime import datetime
 import gettext
 import gettext
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
 import builtins
 import builtins
@@ -31,6 +20,15 @@ if '_' not in builtins.__dict__:
     _ = gettext.gettext
     _ = gettext.gettext
 
 
 
 
+class GracefulException(Exception):
+    # Graceful Exception raised when the user is requesting to cancel the current threaded task
+    def __init__(self):
+        super().__init__()
+
+    def __str__(self):
+        return '\n\n%s' % _("The user requested a graceful exit of the current task.")
+
+
 class LoudDict(dict):
 class LoudDict(dict):
     """
     """
     A Dictionary with a callback for
     A Dictionary with a callback for
@@ -95,2752 +93,6 @@ class FCSignal:
                   'from signal %s' % (func, self))
                   'from signal %s' % (func, self))
 
 
 
 
-class BookmarkManager(QtWidgets.QWidget):
-
-    mark_rows = QtCore.pyqtSignal()
-
-    def __init__(self, app, storage, parent=None):
-        super(BookmarkManager, self).__init__(parent)
-
-        self.app = app
-
-        assert isinstance(storage, dict), "Storage argument is not a dictionary"
-
-        self.bm_dict = deepcopy(storage)
-
-        # Icon and title
-        # self.setWindowIcon(parent.app_icon)
-        # self.setWindowTitle(_("Bookmark Manager"))
-        # self.resize(600, 400)
-
-        # title = QtWidgets.QLabel(
-        #     "<font size=8><B>FlatCAM</B></font><BR>"
-        # )
-        # title.setOpenExternalLinks(True)
-
-        # layouts
-        layout = QtWidgets.QVBoxLayout()
-        self.setLayout(layout)
-
-        table_hlay = QtWidgets.QHBoxLayout()
-        layout.addLayout(table_hlay)
-
-        self.table_widget = FCTable(drag_drop=True, protected_rows=[0, 1])
-        self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
-        table_hlay.addWidget(self.table_widget)
-
-        self.table_widget.setColumnCount(3)
-        self.table_widget.setColumnWidth(0, 20)
-        self.table_widget.setHorizontalHeaderLabels(
-            [
-                '#',
-                _('Title'),
-                _('Web Link')
-            ]
-        )
-        self.table_widget.horizontalHeaderItem(0).setToolTip(
-            _("Index.\n"
-              "The rows in gray color will populate the Bookmarks menu.\n"
-              "The number of gray colored rows is set in Preferences."))
-        self.table_widget.horizontalHeaderItem(1).setToolTip(
-            _("Description of the link that is set as an menu action.\n"
-              "Try to keep it short because it is installed as a menu item."))
-        self.table_widget.horizontalHeaderItem(2).setToolTip(
-            _("Web Link. E.g: https://your_website.org "))
-
-        # pal = QtGui.QPalette()
-        # pal.setColor(QtGui.QPalette.Background, Qt.white)
-
-        # New Bookmark
-        new_vlay = QtWidgets.QVBoxLayout()
-        layout.addLayout(new_vlay)
-
-        new_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Bookmark"))
-        new_vlay.addWidget(new_title_lbl)
-
-        form0 = QtWidgets.QFormLayout()
-        new_vlay.addLayout(form0)
-
-        title_lbl = QtWidgets.QLabel('%s:' % _("Title"))
-        self.title_entry = FCEntry()
-        form0.addRow(title_lbl, self.title_entry)
-
-        link_lbl = QtWidgets.QLabel('%s:' % _("Web Link"))
-        self.link_entry = FCEntry()
-        self.link_entry.set_value('http://')
-        form0.addRow(link_lbl, self.link_entry)
-
-        # Buttons Layout
-        button_hlay = QtWidgets.QHBoxLayout()
-        layout.addLayout(button_hlay)
-
-        add_entry_btn = FCButton(_("Add Entry"))
-        remove_entry_btn = FCButton(_("Remove Entry"))
-        export_list_btn = FCButton(_("Export List"))
-        import_list_btn = FCButton(_("Import List"))
-        # closebtn = QtWidgets.QPushButton(_("Close"))
-
-        # button_hlay.addStretch()
-        button_hlay.addWidget(add_entry_btn)
-        button_hlay.addWidget(remove_entry_btn)
-
-        button_hlay.addWidget(export_list_btn)
-        button_hlay.addWidget(import_list_btn)
-        # button_hlay.addWidget(closebtn)
-        # ##############################################################################
-        # ######################## SIGNALS #############################################
-        # ##############################################################################
-
-        add_entry_btn.clicked.connect(self.on_add_entry)
-        remove_entry_btn.clicked.connect(self.on_remove_entry)
-        export_list_btn.clicked.connect(self.on_export_bookmarks)
-        import_list_btn.clicked.connect(self.on_import_bookmarks)
-        self.title_entry.returnPressed.connect(self.on_add_entry)
-        self.link_entry.returnPressed.connect(self.on_add_entry)
-        # closebtn.clicked.connect(self.accept)
-
-        self.table_widget.drag_drop_sig.connect(self.mark_table_rows_for_actions)
-        self.build_bm_ui()
-
-    def build_bm_ui(self):
-
-        self.table_widget.setRowCount(len(self.bm_dict))
-
-        nr_crt = 0
-        sorted_bookmarks = sorted(list(self.bm_dict.items()), key=lambda x: int(x[0]))
-        for entry, bookmark in sorted_bookmarks:
-            row = nr_crt
-            nr_crt += 1
-
-            title = bookmark[0]
-            weblink = bookmark[1]
-
-            id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt))
-            # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-            self.table_widget.setItem(row, 0, id_item)  # Tool name/id
-
-            title_item = QtWidgets.QTableWidgetItem(title)
-            self.table_widget.setItem(row, 1, title_item)
-
-            weblink_txt = QtWidgets.QTextBrowser()
-            weblink_txt.setOpenExternalLinks(True)
-            weblink_txt.setFrameStyle(QtWidgets.QFrame.NoFrame)
-            weblink_txt.document().setDefaultStyleSheet("a{ text-decoration: none; }")
-
-            weblink_txt.setHtml('<a href=%s>%s</a>' % (weblink, weblink))
-
-            self.table_widget.setCellWidget(row, 2, weblink_txt)
-
-            vertical_header = self.table_widget.verticalHeader()
-            vertical_header.hide()
-
-            horizontal_header = self.table_widget.horizontalHeader()
-            horizontal_header.setMinimumSectionSize(10)
-            horizontal_header.setDefaultSectionSize(70)
-            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-            horizontal_header.resizeSection(0, 20)
-            horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
-            horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
-
-        self.mark_table_rows_for_actions()
-
-        self.app.defaults["global_bookmarks"].clear()
-        for key, val in self.bm_dict.items():
-            self.app.defaults["global_bookmarks"][key] = deepcopy(val)
-
-    def on_add_entry(self, **kwargs):
-        """
-        Add a entry in the Bookmark Table and in the menu actions
-        :return: None
-        """
-        if 'title' in kwargs:
-            title = kwargs['title']
-        else:
-            title = self.title_entry.get_value()
-        if title == '':
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Title entry is empty."))
-            return 'fail'
-
-        if 'link' in kwargs:
-            link = kwargs['link']
-        else:
-            link = self.link_entry.get_value()
-
-        if link == 'http://':
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Web link entry is empty."))
-            return 'fail'
-
-        # if 'http' not in link or 'https' not in link:
-        #     link = 'http://' + link
-
-        for bookmark in self.bm_dict.values():
-            if title == bookmark[0] or link == bookmark[1]:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Either the Title or the Weblink already in the table."))
-                return 'fail'
-
-        # for some reason if the last char in the weblink is a slash it does not make the link clickable
-        # so I remove it
-        if link[-1] == '/':
-            link = link[:-1]
-        # add the new entry to storage
-        new_entry = len(self.bm_dict) + 1
-        self.bm_dict[str(new_entry)] = [title, link]
-
-        # add the link to the menu but only if it is within the set limit
-        bm_limit = int(self.app.defaults["global_bookmarks_limit"])
-        if len(self.bm_dict) < bm_limit:
-            act = QtWidgets.QAction(parent=self.app.ui.menuhelp_bookmarks)
-            act.setText(title)
-            act.setIcon(QtGui.QIcon(self.app.resource_location + '/link16.png'))
-            act.triggered.connect(lambda: webbrowser.open(link))
-            self.app.ui.menuhelp_bookmarks.insertAction(self.app.ui.menuhelp_bookmarks_manager, act)
-
-        self.app.inform.emit('[success] %s' % _("Bookmark added."))
-
-        # add the new entry to the bookmark manager table
-        self.build_bm_ui()
-
-    def on_remove_entry(self):
-        """
-        Remove an Entry in the Bookmark table and from the menu actions
-        :return:
-        """
-        index_list = []
-        for model_index in self.table_widget.selectionModel().selectedRows():
-            index = QtCore.QPersistentModelIndex(model_index)
-            index_list.append(index)
-            title_to_remove = self.table_widget.item(model_index.row(), 1).text()
-
-            if title_to_remove == 'FlatCAM' or title_to_remove == 'Backup Site':
-                self.app.inform.emit('[WARNING_NOTCL] %s.' % _("This bookmark can not be removed"))
-                self.build_bm_ui()
-                return
-            else:
-                for k, bookmark in list(self.bm_dict.items()):
-                    if title_to_remove == bookmark[0]:
-                        # remove from the storage
-                        self.bm_dict.pop(k, None)
-
-                        for act in self.app.ui.menuhelp_bookmarks.actions():
-                            if act.text() == title_to_remove:
-                                # disconnect the signal
-                                try:
-                                    act.triggered.disconnect()
-                                except TypeError:
-                                    pass
-                                # remove the action from the menu
-                                self.app.ui.menuhelp_bookmarks.removeAction(act)
-
-        # house keeping: it pays to have keys increased by one
-        new_key = 0
-        new_dict = {}
-        for k, v in self.bm_dict.items():
-            # we start with key 1 so we can use the len(self.bm_dict)
-            # when adding bookmarks (keys in bm_dict)
-            new_key += 1
-            new_dict[str(new_key)] = v
-
-        self.bm_dict = deepcopy(new_dict)
-        new_dict.clear()
-
-        self.app.inform.emit('[success] %s' % _("Bookmark removed."))
-
-        # for index in index_list:
-        #     self.table_widget.model().removeRow(index.row())
-        self.build_bm_ui()
-
-    def on_export_bookmarks(self):
-        self.app.report_usage("on_export_bookmarks")
-        self.app.log.debug("on_export_bookmarks()")
-
-        date = str(datetime.today()).rpartition('.')[0]
-        date = ''.join(c for c in date if c not in ':-')
-        date = date.replace(' ', '_')
-
-        filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = FCFileSaveDialog.get_saved_filename( caption=_("Export FlatCAM Bookmarks"),
-                                                             directory='{l_save}/FlatCAM_{n}_{date}'.format(
-                                                                 l_save=str(self.app.get_last_save_folder()),
-                                                                 n=_("Bookmarks"),
-                                                                 date=date),
-                                                             filter=filter__)
-
-        filename = str(filename)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-            return
-        else:
-            try:
-                f = open(filename, 'w')
-                f.close()
-            except PermissionError:
-                self.app.inform.emit('[WARNING] %s' %
-                                     _("Permission denied, saving not possible.\n"
-                                       "Most likely another app is holding the file open and not accessible."))
-                return
-            except IOError:
-                self.app.log.debug('Creating a new bookmarks file ...')
-                f = open(filename, 'w')
-                f.close()
-            except Exception:
-                e = sys.exc_info()[0]
-                self.app.log.error("Could not load defaults file.")
-                self.app.log.error(str(e))
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file."))
-                return
-
-            # Save Bookmarks to a file
-            try:
-                with open(filename, "w") as f:
-                    for title, link in self.bm_dict.items():
-                        line2write = str(title) + ':' + str(link) + '\n'
-                        f.write(line2write)
-            except Exception:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write bookmarks to file."))
-                return
-        self.app.inform.emit('[success] %s: %s' % (_("Exported bookmarks to"), filename))
-
-    def on_import_bookmarks(self):
-        self.app.log.debug("on_import_bookmarks()")
-
-        filter_ = "Text File (*.txt);;All Files (*.*)"
-        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Bookmarks"), filter=filter_)
-
-        filename = str(filename)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-        else:
-            try:
-                with open(filename) as f:
-                    bookmarks = f.readlines()
-            except IOError:
-                self.app.log.error("Could not load bookmarks file.")
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file."))
-                return
-
-            for line in bookmarks:
-                proc_line = line.replace(' ', '').partition(':')
-                self.on_add_entry(title=proc_line[0], link=proc_line[2])
-
-            self.app.inform.emit('[success] %s: %s' % (_("Imported Bookmarks from"), filename))
-
-    def mark_table_rows_for_actions(self):
-        for row in range(self.table_widget.rowCount()):
-            item_to_paint = self.table_widget.item(row, 0)
-            if row < self.app.defaults["global_bookmarks_limit"]:
-                item_to_paint.setBackground(QtGui.QColor('gray'))
-                # item_to_paint.setForeground(QtGui.QColor('black'))
-            else:
-                item_to_paint.setBackground(QtGui.QColor('white'))
-                # item_to_paint.setForeground(QtGui.QColor('black'))
-
-    def rebuild_actions(self):
-        # rebuild the storage to reflect the order of the lines
-        self.bm_dict.clear()
-        for row in range(self.table_widget.rowCount()):
-            title = self.table_widget.item(row, 1).text()
-            wlink = self.table_widget.cellWidget(row, 2).toPlainText()
-
-            entry = int(row) + 1
-            self.bm_dict.update(
-                {
-                    str(entry): [title, wlink]
-                }
-            )
-
-        self.app.install_bookmarks(book_dict=self.bm_dict)
-
-    # def accept(self):
-    #     self.rebuild_actions()
-    #     super().accept()
-
-    def closeEvent(self, QCloseEvent):
-        self.rebuild_actions()
-        super().closeEvent(QCloseEvent)
-
-
-class ToolsDB(QtWidgets.QWidget):
-
-    mark_tools_rows = QtCore.pyqtSignal()
-
-    def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
-        super(ToolsDB, self).__init__(parent)
-
-        self.app = app
-        self.decimals = 4
-        self.callback_app = callback_on_edited
-
-        self.on_tool_request = callback_on_tool_request
-
-        self.offset_item_options = ["Path", "In", "Out", "Custom"]
-        self.type_item_options = ["Iso", "Rough", "Finish"]
-        self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
-
-        '''
-        dict to hold all the tools in the Tools DB
-        format:
-        {
-            tool_id: {
-                'name': 'new_tool'
-                'tooldia': self.app.defaults["geometry_cnctooldia"]
-                'offset': 'Path'
-                'offset_value': 0.0
-                'type':  _('Rough'),
-                'tool_type': 'C1'
-                'data': dict()
-            }
-        }
-        '''
-        self.db_tool_dict = {}
-
-        # layouts
-        layout = QtWidgets.QVBoxLayout()
-        self.setLayout(layout)
-
-        table_hlay = QtWidgets.QHBoxLayout()
-        layout.addLayout(table_hlay)
-
-        self.table_widget = FCTable(drag_drop=True)
-        self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
-        table_hlay.addWidget(self.table_widget)
-
-        # set the number of columns and the headers tool tips
-        self.configure_table()
-
-        # pal = QtGui.QPalette()
-        # pal.setColor(QtGui.QPalette.Background, Qt.white)
-
-        # New Bookmark
-        new_vlay = QtWidgets.QVBoxLayout()
-        layout.addLayout(new_vlay)
-
-        # new_tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Tool"))
-        # new_vlay.addWidget(new_tool_lbl, alignment=QtCore.Qt.AlignBottom)
-
-        self.buttons_frame = QtWidgets.QFrame()
-        self.buttons_frame.setContentsMargins(0, 0, 0, 0)
-        layout.addWidget(self.buttons_frame)
-        self.buttons_box = QtWidgets.QHBoxLayout()
-        self.buttons_box.setContentsMargins(0, 0, 0, 0)
-        self.buttons_frame.setLayout(self.buttons_box)
-        self.buttons_frame.show()
-
-        add_entry_btn = FCButton(_("Add Geometry Tool in DB"))
-        add_entry_btn.setToolTip(
-            _("Add a new tool in the Tools Database.\n"
-              "It will be used in the Geometry UI.\n"
-              "You can edit it after it is added.")
-        )
-        self.buttons_box.addWidget(add_entry_btn)
-
-        # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
-        # add_fct_entry_btn.setToolTip(
-        #     _("Add a new tool in the Tools Database.\n"
-        #       "It will be used in the Paint/NCC Tools UI.\n"
-        #       "You can edit it after it is added.")
-        # )
-        # self.buttons_box.addWidget(add_fct_entry_btn)
-
-        remove_entry_btn = FCButton(_("Delete Tool from DB"))
-        remove_entry_btn.setToolTip(
-            _("Remove a selection of tools in the Tools Database.")
-        )
-        self.buttons_box.addWidget(remove_entry_btn)
-
-        export_db_btn = FCButton(_("Export DB"))
-        export_db_btn.setToolTip(
-            _("Save the Tools Database to a custom text file.")
-        )
-        self.buttons_box.addWidget(export_db_btn)
-
-        import_db_btn = FCButton(_("Import DB"))
-        import_db_btn.setToolTip(
-            _("Load the Tools Database information's from a custom text file.")
-        )
-        self.buttons_box.addWidget(import_db_btn)
-
-        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
-        self.add_tool_from_db.setToolTip(
-            _("Add a new tool in the Tools Table of the\n"
-              "active Geometry object after selecting a tool\n"
-              "in the Tools Database.")
-        )
-        self.add_tool_from_db.hide()
-
-        self.cancel_tool_from_db = FCButton(_("Cancel"))
-        self.cancel_tool_from_db.hide()
-
-        hlay = QtWidgets.QHBoxLayout()
-        layout.addLayout(hlay)
-        hlay.addWidget(self.add_tool_from_db)
-        hlay.addWidget(self.cancel_tool_from_db)
-        hlay.addStretch()
-
-        # ##############################################################################
-        # ######################## SIGNALS #############################################
-        # ##############################################################################
-
-        add_entry_btn.clicked.connect(self.on_tool_add)
-        remove_entry_btn.clicked.connect(self.on_tool_delete)
-        export_db_btn.clicked.connect(self.on_export_tools_db_file)
-        import_db_btn.clicked.connect(self.on_import_tools_db_file)
-        # closebtn.clicked.connect(self.accept)
-
-        self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
-        self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
-
-        self.setup_db_ui()
-
-    def configure_table(self):
-        self.table_widget.setColumnCount(27)
-        # self.table_widget.setColumnWidth(0, 20)
-        self.table_widget.setHorizontalHeaderLabels(
-            [
-                '#',
-                _("Tool Name"),
-                _("Tool Dia"),
-                _("Tool Offset"),
-                _("Custom Offset"),
-                _("Tool Type"),
-                _("Tool Shape"),
-                _("Cut Z"),
-                _("MultiDepth"),
-                _("DPP"),
-                _("V-Dia"),
-                _("V-Angle"),
-                _("Travel Z"),
-                _("FR"),
-                _("FR Z"),
-                _("FR Rapids"),
-                _("Spindle Speed"),
-                _("Dwell"),
-                _("Dwelltime"),
-                _("Preprocessor"),
-                _("ExtraCut"),
-                _("E-Cut Length"),
-                _("Toolchange"),
-                _("Toolchange XY"),
-                _("Toolchange Z"),
-                _("Start Z"),
-                _("End Z"),
-            ]
-        )
-        self.table_widget.horizontalHeaderItem(0).setToolTip(
-            _("Tool Index."))
-        self.table_widget.horizontalHeaderItem(1).setToolTip(
-            _("Tool name.\n"
-              "This is not used in the app, it's function\n"
-              "is to serve as a note for the user."))
-        self.table_widget.horizontalHeaderItem(2).setToolTip(
-            _("Tool Diameter."))
-        self.table_widget.horizontalHeaderItem(3).setToolTip(
-            _("Tool Offset.\n"
-              "Can be of a few types:\n"
-              "Path = zero offset\n"
-              "In = offset inside by half of tool diameter\n"
-              "Out = offset outside by half of tool diameter\n"
-              "Custom = custom offset using the Custom Offset value"))
-        self.table_widget.horizontalHeaderItem(4).setToolTip(
-            _("Custom Offset.\n"
-              "A value to be used as offset from the current path."))
-        self.table_widget.horizontalHeaderItem(5).setToolTip(
-            _("Tool Type.\n"
-              "Can be:\n"
-              "Iso = isolation cut\n"
-              "Rough = rough cut, low feedrate, multiple passes\n"
-              "Finish = finishing cut, high feedrate"))
-        self.table_widget.horizontalHeaderItem(6).setToolTip(
-            _("Tool Shape. \n"
-              "Can be:\n"
-              "C1 ... C4 = circular tool with x flutes\n"
-              "B = ball tip milling tool\n"
-              "V = v-shape milling tool"))
-        self.table_widget.horizontalHeaderItem(7).setToolTip(
-            _("Cutting Depth.\n"
-              "The depth at which to cut into material."))
-        self.table_widget.horizontalHeaderItem(8).setToolTip(
-            _("Multi Depth.\n"
-              "Selecting this will allow cutting in multiple passes,\n"
-              "each pass adding a DPP parameter depth."))
-        self.table_widget.horizontalHeaderItem(9).setToolTip(
-            _("DPP. Depth per Pass.\n"
-              "The value used to cut into material on each pass."))
-        self.table_widget.horizontalHeaderItem(10).setToolTip(
-            _("V-Dia.\n"
-              "Diameter of the tip for V-Shape Tools."))
-        self.table_widget.horizontalHeaderItem(11).setToolTip(
-            _("V-Agle.\n"
-              "Angle at the tip for the V-Shape Tools."))
-        self.table_widget.horizontalHeaderItem(12).setToolTip(
-            _("Clearance Height.\n"
-              "Height at which the milling bit will travel between cuts,\n"
-              "above the surface of the material, avoiding all fixtures."))
-        self.table_widget.horizontalHeaderItem(13).setToolTip(
-            _("FR. Feedrate\n"
-              "The speed on XY plane used while cutting into material."))
-        self.table_widget.horizontalHeaderItem(14).setToolTip(
-            _("FR Z. Feedrate Z\n"
-              "The speed on Z plane."))
-        self.table_widget.horizontalHeaderItem(15).setToolTip(
-            _("FR Rapids. Feedrate Rapids\n"
-              "Speed used while moving as fast as possible.\n"
-              "This is used only by some devices that can't use\n"
-              "the G0 g-code command. Mostly 3D printers."))
-        self.table_widget.horizontalHeaderItem(16).setToolTip(
-            _("Spindle Speed.\n"
-              "If it's left empty it will not be used.\n"
-              "The speed of the spindle in RPM."))
-        self.table_widget.horizontalHeaderItem(17).setToolTip(
-            _("Dwell.\n"
-              "Check this if a delay is needed to allow\n"
-              "the spindle motor to reach it's set speed."))
-        self.table_widget.horizontalHeaderItem(18).setToolTip(
-            _("Dwell Time.\n"
-              "A delay used to allow the motor spindle reach it's set speed."))
-        self.table_widget.horizontalHeaderItem(19).setToolTip(
-            _("Preprocessor.\n"
-              "A selection of files that will alter the generated G-code\n"
-              "to fit for a number of use cases."))
-        self.table_widget.horizontalHeaderItem(20).setToolTip(
-            _("Extra Cut.\n"
-              "If checked, after a isolation is finished an extra cut\n"
-              "will be added where the start and end of isolation meet\n"
-              "such as that this point is covered by this extra cut to\n"
-              "ensure a complete isolation."))
-        self.table_widget.horizontalHeaderItem(21).setToolTip(
-            _("Extra Cut length.\n"
-              "If checked, after a isolation is finished an extra cut\n"
-              "will be added where the start and end of isolation meet\n"
-              "such as that this point is covered by this extra cut to\n"
-              "ensure a complete isolation. This is the length of\n"
-              "the extra cut."))
-        self.table_widget.horizontalHeaderItem(22).setToolTip(
-            _("Toolchange.\n"
-              "It will create a toolchange event.\n"
-              "The kind of toolchange is determined by\n"
-              "the preprocessor file."))
-        self.table_widget.horizontalHeaderItem(23).setToolTip(
-            _("Toolchange XY.\n"
-              "A set of coordinates in the format (x, y).\n"
-              "Will determine the cartesian position of the point\n"
-              "where the tool change event take place."))
-        self.table_widget.horizontalHeaderItem(24).setToolTip(
-            _("Toolchange Z.\n"
-              "The position on Z plane where the tool change event take place."))
-        self.table_widget.horizontalHeaderItem(25).setToolTip(
-            _("Start Z.\n"
-              "If it's left empty it will not be used.\n"
-              "A position on Z plane to move immediately after job start."))
-        self.table_widget.horizontalHeaderItem(26).setToolTip(
-            _("End Z.\n"
-              "A position on Z plane to move immediately after job stop."))
-
-    def setup_db_ui(self):
-        filename = self.app.data_path + '/geo_tools_db.FlatDB'
-
-        # load the database tools from the file
-        try:
-            with open(filename) as f:
-                tools = f.read()
-        except IOError:
-            self.app.log.error("Could not load tools DB file.")
-            self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
-            return
-
-        try:
-            self.db_tool_dict = json.loads(tools)
-        except Exception:
-            e = sys.exc_info()[0]
-            self.app.log.error(str(e))
-            self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
-            return
-
-        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
-
-        self.build_db_ui()
-
-        self.table_widget.setupContextMenu()
-        self.table_widget.addContextMenu(
-            _("Add to DB"), self.on_tool_add, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png"))
-        self.table_widget.addContextMenu(
-            _("Copy from DB"), self.on_tool_copy, icon=QtGui.QIcon(self.app.resource_location + "/copy16.png"))
-        self.table_widget.addContextMenu(
-            _("Delete from DB"), self.on_tool_delete, icon=QtGui.QIcon(self.app.resource_location + "/delete32.png"))
-
-    def build_db_ui(self):
-        self.ui_disconnect()
-        self.table_widget.setRowCount(len(self.db_tool_dict))
-
-        nr_crt = 0
-
-        for toolid, dict_val in self.db_tool_dict.items():
-            row = nr_crt
-            nr_crt += 1
-
-            t_name = dict_val['name']
-            try:
-                self.add_tool_table_line(row, name=t_name, widget=self.table_widget, tooldict=dict_val)
-            except Exception as e:
-                self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
-            vertical_header = self.table_widget.verticalHeader()
-            vertical_header.hide()
-
-            horizontal_header = self.table_widget.horizontalHeader()
-            horizontal_header.setMinimumSectionSize(10)
-            horizontal_header.setDefaultSectionSize(70)
-
-            self.table_widget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
-            for x in range(27):
-                self.table_widget.resizeColumnToContents(x)
-
-            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-            # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
-            # horizontal_header.setSectionResizeMode(13, QtWidgets.QHeaderView.Fixed)
-
-            horizontal_header.resizeSection(0, 20)
-            # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
-            # horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
-
-        self.ui_connect()
-
-    def add_tool_table_line(self, row, name, widget, tooldict):
-        data = tooldict['data']
-
-        nr_crt = row + 1
-        id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt))
-        # id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-        flags = id_item.flags() & ~QtCore.Qt.ItemIsEditable
-        id_item.setFlags(flags)
-        widget.setItem(row, 0, id_item)  # Tool name/id
-
-        tool_name_item = QtWidgets.QTableWidgetItem(name)
-        widget.setItem(row, 1, tool_name_item)
-
-        dia_item = FCDoubleSpinner()
-        dia_item.set_precision(self.decimals)
-        dia_item.setSingleStep(0.1)
-        dia_item.set_range(0.0, 9999.9999)
-        dia_item.set_value(float(tooldict['tooldia']))
-        widget.setCellWidget(row, 2, dia_item)
-
-        tool_offset_item = FCComboBox()
-        for item in self.offset_item_options:
-            tool_offset_item.addItem(item)
-        tool_offset_item.set_value(tooldict['offset'])
-        widget.setCellWidget(row, 3, tool_offset_item)
-
-        c_offset_item = FCDoubleSpinner()
-        c_offset_item.set_precision(self.decimals)
-        c_offset_item.setSingleStep(0.1)
-        c_offset_item.set_range(-9999.9999, 9999.9999)
-        c_offset_item.set_value(float(tooldict['offset_value']))
-        widget.setCellWidget(row, 4, c_offset_item)
-
-        tt_item = FCComboBox()
-        for item in self.type_item_options:
-            tt_item.addItem(item)
-        tt_item.set_value(tooldict['type'])
-        widget.setCellWidget(row, 5, tt_item)
-
-        tshape_item = FCComboBox()
-        for item in self.tool_type_item_options:
-            tshape_item.addItem(item)
-        tshape_item.set_value(tooldict['tool_type'])
-        widget.setCellWidget(row, 6, tshape_item)
-
-        cutz_item = FCDoubleSpinner()
-        cutz_item.set_precision(self.decimals)
-        cutz_item.setSingleStep(0.1)
-        if self.app.defaults['global_machinist_setting']:
-            cutz_item.set_range(-9999.9999, 9999.9999)
-        else:
-            cutz_item.set_range(-9999.9999, -0.0000)
-
-        cutz_item.set_value(float(data['cutz']))
-        widget.setCellWidget(row, 7, cutz_item)
-
-        multidepth_item = FCCheckBox()
-        multidepth_item.set_value(data['multidepth'])
-        widget.setCellWidget(row, 8, multidepth_item)
-
-        # to make the checkbox centered but it can no longer have it's value accessed - needs a fix using findchild()
-        # multidepth_item = QtWidgets.QWidget()
-        # cb = FCCheckBox()
-        # cb.set_value(data['multidepth'])
-        # qhboxlayout = QtWidgets.QHBoxLayout(multidepth_item)
-        # qhboxlayout.addWidget(cb)
-        # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
-        # qhboxlayout.setContentsMargins(0, 0, 0, 0)
-        # widget.setCellWidget(row, 8, multidepth_item)
-
-        depth_per_pass_item = FCDoubleSpinner()
-        depth_per_pass_item.set_precision(self.decimals)
-        depth_per_pass_item.setSingleStep(0.1)
-        depth_per_pass_item.set_range(0.0, 9999.9999)
-        depth_per_pass_item.set_value(float(data['depthperpass']))
-        widget.setCellWidget(row, 9, depth_per_pass_item)
-
-        vtip_dia_item = FCDoubleSpinner()
-        vtip_dia_item.set_precision(self.decimals)
-        vtip_dia_item.setSingleStep(0.1)
-        vtip_dia_item.set_range(0.0, 9999.9999)
-        vtip_dia_item.set_value(float(data['vtipdia']))
-        widget.setCellWidget(row, 10, vtip_dia_item)
-
-        vtip_angle_item = FCDoubleSpinner()
-        vtip_angle_item.set_precision(self.decimals)
-        vtip_angle_item.setSingleStep(0.1)
-        vtip_angle_item.set_range(-360.0, 360.0)
-        vtip_angle_item.set_value(float(data['vtipangle']))
-        widget.setCellWidget(row, 11, vtip_angle_item)
-
-        travelz_item = FCDoubleSpinner()
-        travelz_item.set_precision(self.decimals)
-        travelz_item.setSingleStep(0.1)
-        if self.app.defaults['global_machinist_setting']:
-            travelz_item.set_range(-9999.9999, 9999.9999)
-        else:
-            travelz_item.set_range(0.0000, 9999.9999)
-
-        travelz_item.set_value(float(data['travelz']))
-        widget.setCellWidget(row, 12, travelz_item)
-
-        fr_item = FCDoubleSpinner()
-        fr_item.set_precision(self.decimals)
-        fr_item.set_range(0.0, 9999.9999)
-        fr_item.set_value(float(data['feedrate']))
-        widget.setCellWidget(row, 13, fr_item)
-
-        frz_item = FCDoubleSpinner()
-        frz_item.set_precision(self.decimals)
-        frz_item.set_range(0.0, 9999.9999)
-        frz_item.set_value(float(data['feedrate_z']))
-        widget.setCellWidget(row, 14, frz_item)
-
-        frrapids_item = FCDoubleSpinner()
-        frrapids_item.set_precision(self.decimals)
-        frrapids_item.set_range(0.0, 9999.9999)
-        frrapids_item.set_value(float(data['feedrate_rapid']))
-        widget.setCellWidget(row, 15, frrapids_item)
-
-        spindlespeed_item = FCSpinner()
-        spindlespeed_item.set_range(0, 1000000)
-        spindlespeed_item.set_value(int(data['spindlespeed']))
-        spindlespeed_item.set_step(100)
-        widget.setCellWidget(row, 16, spindlespeed_item)
-
-        dwell_item = FCCheckBox()
-        dwell_item.set_value(data['dwell'])
-        widget.setCellWidget(row, 17, dwell_item)
-
-        dwelltime_item = FCDoubleSpinner()
-        dwelltime_item.set_precision(self.decimals)
-        dwelltime_item.set_range(0.0000, 9999.9999)
-        dwelltime_item.set_value(float(data['dwelltime']))
-        widget.setCellWidget(row, 18, dwelltime_item)
-
-        pp_item = FCComboBox()
-        for item in self.app.preprocessors:
-            pp_item.addItem(item)
-        pp_item.set_value(data['ppname_g'])
-        widget.setCellWidget(row, 19, pp_item)
-
-        ecut_item = FCCheckBox()
-        ecut_item.set_value(data['extracut'])
-        widget.setCellWidget(row, 20, ecut_item)
-
-        ecut_length_item = FCDoubleSpinner()
-        ecut_length_item.set_precision(self.decimals)
-        ecut_length_item.set_range(0.0000, 9999.9999)
-        ecut_length_item.set_value(data['extracut_length'])
-        widget.setCellWidget(row, 21, ecut_length_item)
-
-        toolchange_item = FCCheckBox()
-        toolchange_item.set_value(data['toolchange'])
-        widget.setCellWidget(row, 22, toolchange_item)
-
-        toolchangexy_item = QtWidgets.QTableWidgetItem(str(data['toolchangexy']) if data['toolchangexy'] else '')
-        widget.setItem(row, 23, toolchangexy_item)
-
-        toolchangez_item = FCDoubleSpinner()
-        toolchangez_item.set_precision(self.decimals)
-        toolchangez_item.setSingleStep(0.1)
-        if self.app.defaults['global_machinist_setting']:
-            toolchangez_item.set_range(-9999.9999, 9999.9999)
-        else:
-            toolchangez_item.set_range(0.0000, 9999.9999)
-
-        toolchangez_item.set_value(float(data['toolchangez']))
-        widget.setCellWidget(row, 24, toolchangez_item)
-
-        startz_item = QtWidgets.QTableWidgetItem(str(data['startz']) if data['startz'] else '')
-        widget.setItem(row, 25, startz_item)
-
-        endz_item = FCDoubleSpinner()
-        endz_item.set_precision(self.decimals)
-        endz_item.setSingleStep(0.1)
-        if self.app.defaults['global_machinist_setting']:
-            endz_item.set_range(-9999.9999, 9999.9999)
-        else:
-            endz_item.set_range(0.0000, 9999.9999)
-
-        endz_item.set_value(float(data['endz']))
-        widget.setCellWidget(row, 26, endz_item)
-
-    def on_tool_add(self):
-        """
-        Add a tool in the DB Tool Table
-        :return: None
-        """
-
-        default_data = {}
-        default_data.update({
-            "cutz": float(self.app.defaults["geometry_cutz"]),
-            "multidepth": self.app.defaults["geometry_multidepth"],
-            "depthperpass": float(self.app.defaults["geometry_depthperpass"]),
-            "vtipdia": float(self.app.defaults["geometry_vtipdia"]),
-            "vtipangle": float(self.app.defaults["geometry_vtipangle"]),
-            "travelz": float(self.app.defaults["geometry_travelz"]),
-            "feedrate": float(self.app.defaults["geometry_feedrate"]),
-            "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]),
-            "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]),
-            "spindlespeed": self.app.defaults["geometry_spindlespeed"],
-            "dwell": self.app.defaults["geometry_dwell"],
-            "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
-            "ppname_g": self.app.defaults["geometry_ppname_g"],
-            "extracut": self.app.defaults["geometry_extracut"],
-            "extracut_length": float(self.app.defaults["geometry_extracut_length"]),
-            "toolchange": self.app.defaults["geometry_toolchange"],
-            "toolchangexy": self.app.defaults["geometry_toolchangexy"],
-            "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
-            "startz": self.app.defaults["geometry_startz"],
-            "endz": float(self.app.defaults["geometry_endz"])
-        })
-
-        dict_elem = {}
-        dict_elem['name'] = 'new_tool'
-        if type(self.app.defaults["geometry_cnctooldia"]) == float:
-            dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
-        else:
-            try:
-                tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
-                tools_diameters = [eval(a) for a in tools_string if a != '']
-                dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
-            except Exception as e:
-                self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
-                return
-
-        dict_elem['offset'] = 'Path'
-        dict_elem['offset_value'] = 0.0
-        dict_elem['type'] = 'Rough'
-        dict_elem['tool_type'] = 'C1'
-        dict_elem['data'] = default_data
-
-        new_toolid = len(self.db_tool_dict) + 1
-        self.db_tool_dict[new_toolid] = deepcopy(dict_elem)
-
-        # add the new entry to the Tools DB table
-        self.build_db_ui()
-        self.callback_on_edited()
-        self.app.inform.emit('[success] %s' % _("Tool added to DB."))
-
-    def on_tool_copy(self):
-        """
-        Copy a selection of Tools in the Tools DB table
-        :return:
-        """
-        new_tool_id = self.table_widget.rowCount() + 1
-        for model_index in self.table_widget.selectionModel().selectedRows():
-            # index = QtCore.QPersistentModelIndex(model_index)
-            old_tool_id = self.table_widget.item(model_index.row(), 0).text()
-            new_tool_id += 1
-
-            for toolid, dict_val in list(self.db_tool_dict.items()):
-                if int(old_tool_id) == int(toolid):
-                    self.db_tool_dict.update({
-                        new_tool_id: deepcopy(dict_val)
-                    })
-
-        self.build_db_ui()
-        self.callback_on_edited()
-        self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
-
-    def on_tool_delete(self):
-        """
-        Delete a selection of Tools in the Tools DB table
-        :return:
-        """
-        for model_index in self.table_widget.selectionModel().selectedRows():
-            # index = QtCore.QPersistentModelIndex(model_index)
-            toolname_to_remove = self.table_widget.item(model_index.row(), 0).text()
-
-            for toolid, dict_val in list(self.db_tool_dict.items()):
-                if int(toolname_to_remove) == int(toolid):
-                    # remove from the storage
-                    self.db_tool_dict.pop(toolid, None)
-
-        self.build_db_ui()
-        self.callback_on_edited()
-        self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
-
-    def on_export_tools_db_file(self):
-        self.app.report_usage("on_export_tools_db_file")
-        self.app.log.debug("on_export_tools_db_file()")
-
-        date = str(datetime.today()).rpartition('.')[0]
-        date = ''.join(c for c in date if c not in ':-')
-        date = date.replace(' ', '_')
-
-        filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = FCFileSaveDialog.get_saved_filename( caption=_("Export Tools Database"),
-                                                             directory='{l_save}/FlatCAM_{n}_{date}'.format(
-                                                                 l_save=str(self.app.get_last_save_folder()),
-                                                                 n=_("Tools_Database"),
-                                                                 date=date),
-                                                             filter=filter__)
-
-        filename = str(filename)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-            return
-        else:
-            try:
-                f = open(filename, 'w')
-                f.close()
-            except PermissionError:
-                self.app.inform.emit('[WARNING] %s' %
-                                     _("Permission denied, saving not possible.\n"
-                                       "Most likely another app is holding the file open and not accessible."))
-                return
-            except IOError:
-                self.app.log.debug('Creating a new Tools DB file ...')
-                f = open(filename, 'w')
-                f.close()
-            except Exception:
-                e = sys.exc_info()[0]
-                self.app.log.error("Could not load Tools DB file.")
-                self.app.log.error(str(e))
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
-                return
-
-            # Save update options
-            try:
-                # Save Tools DB in a file
-                try:
-                    with open(filename, "w") as f:
-                        json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
-                except Exception as e:
-                    self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
-                    self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                    return
-            except Exception:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                return
-
-        self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
-
-    def on_import_tools_db_file(self):
-        self.app.report_usage("on_import_tools_db_file")
-        self.app.log.debug("on_import_tools_db_file()")
-
-        filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-        else:
-            try:
-                with open(filename) as f:
-                    tools_in_db = f.read()
-            except IOError:
-                self.app.log.error("Could not load Tools DB file.")
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
-                return
-
-            try:
-                self.db_tool_dict = json.loads(tools_in_db)
-            except Exception:
-                e = sys.exc_info()[0]
-                self.app.log.error(str(e))
-                self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
-                return
-
-            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
-            self.build_db_ui()
-            self.callback_on_edited()
-
-    def on_save_tools_db(self, silent=False):
-        self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.")
-
-        filename = self.app.data_path + "/geo_tools_db.FlatDB"
-
-        # Preferences save, update the color of the Tools DB Tab text
-        for idx in range(self.app.ui.plot_tab_area.count()):
-            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
-                self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
-
-                # Save Tools DB in a file
-                try:
-                    f = open(filename, "w")
-                    json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
-                    f.close()
-                except Exception as e:
-                    self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                    return
-
-                if not silent:
-                    self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
-
-    def ui_connect(self):
-        try:
-            try:
-                self.table_widget.itemChanged.disconnect(self.callback_on_edited)
-            except (TypeError, AttributeError):
-                pass
-            self.table_widget.itemChanged.connect(self.callback_on_edited)
-        except AttributeError:
-            pass
-
-        for row in range(self.table_widget.rowCount()):
-            for col in range(self.table_widget.columnCount()):
-                # ComboBox
-                try:
-                    try:
-                        self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
-                    except (TypeError, AttributeError):
-                        pass
-                    self.table_widget.cellWidget(row, col).currentIndexChanged.connect(self.callback_on_edited)
-                except AttributeError:
-                    pass
-
-                # CheckBox
-                try:
-                    try:
-                        self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
-                    except (TypeError, AttributeError):
-                        pass
-                    self.table_widget.cellWidget(row, col).toggled.connect(self.callback_on_edited)
-                except AttributeError:
-                    pass
-
-                # SpinBox, DoubleSpinBox
-                try:
-                    try:
-                        self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
-                    except (TypeError, AttributeError):
-                        pass
-                    self.table_widget.cellWidget(row, col).valueChanged.connect(self.callback_on_edited)
-                except AttributeError:
-                    pass
-
-    def ui_disconnect(self):
-        try:
-            self.table_widget.itemChanged.disconnect(self.callback_on_edited)
-        except (TypeError, AttributeError):
-            pass
-
-        for row in range(self.table_widget.rowCount()):
-            for col in range(self.table_widget.columnCount()):
-                # ComboBox
-                try:
-                    self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
-                except (TypeError, AttributeError):
-                    pass
-
-                # CheckBox
-                try:
-                    self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
-                except (TypeError, AttributeError):
-                    pass
-
-                # SpinBox, DoubleSpinBox
-                try:
-                    self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
-                except (TypeError, AttributeError):
-                    pass
-
-    def callback_on_edited(self):
-
-        # update the dictionary storage self.db_tool_dict
-        self.db_tool_dict.clear()
-        dict_elem = {}
-        default_data = {}
-
-        for row in range(self.table_widget.rowCount()):
-            new_toolid = row + 1
-            for col in range(self.table_widget.columnCount()):
-                column_header_text = self.table_widget.horizontalHeaderItem(col).text()
-                if column_header_text == _('Tool Name'):
-                    dict_elem['name'] = self.table_widget.item(row, col).text()
-                elif column_header_text == _('Tool Dia'):
-                    dict_elem['tooldia'] = self.table_widget.cellWidget(row, col).get_value()
-                elif column_header_text == _('Tool Offset'):
-                    dict_elem['offset'] = self.table_widget.cellWidget(row, col).get_value()
-                elif column_header_text == _('Custom Offset'):
-                    dict_elem['offset_value'] = self.table_widget.cellWidget(row, col).get_value()
-                elif column_header_text == _('Tool Type'):
-                    dict_elem['type'] = self.table_widget.cellWidget(row, col).get_value()
-                elif column_header_text == _('Tool Shape'):
-                    dict_elem['tool_type'] = self.table_widget.cellWidget(row, col).get_value()
-                else:
-                    if column_header_text == _('Cut Z'):
-                        default_data['cutz'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('MultiDepth'):
-                        default_data['multidepth'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('DPP'):
-                        default_data['depthperpass'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('V-Dia'):
-                        default_data['vtipdia'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('V-Angle'):
-                        default_data['vtipangle'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Travel Z'):
-                        default_data['travelz'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('FR'):
-                        default_data['feedrate'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('FR Z'):
-                        default_data['feedrate_z'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('FR Rapids'):
-                        default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Spindle Speed'):
-                        default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Dwell'):
-                        default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Dwelltime'):
-                        default_data['dwelltime'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Preprocessor'):
-                        default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('ExtraCut'):
-                        default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _("E-Cut Length"):
-                        default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Toolchange'):
-                        default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Toolchange XY'):
-                        default_data['toolchangexy'] = self.table_widget.item(row, col).text()
-                    elif column_header_text == _('Toolchange Z'):
-                        default_data['toolchangez'] = self.table_widget.cellWidget(row, col).get_value()
-                    elif column_header_text == _('Start Z'):
-                        default_data['startz'] = float(self.table_widget.item(row, col).text()) \
-                            if self.table_widget.item(row, col).text() != '' else None
-                    elif column_header_text == _('End Z'):
-                        default_data['endz'] = self.table_widget.cellWidget(row, col).get_value()
-
-            dict_elem['data'] = default_data
-            self.db_tool_dict.update(
-                {
-                    new_toolid: deepcopy(dict_elem)
-                }
-            )
-
-        self.callback_app()
-
-    def on_tool_requested_from_app(self):
-        if not self.table_widget.selectionModel().selectedRows():
-            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
-            return
-
-        model_index_list = self.table_widget.selectionModel().selectedRows()
-        for model_index in model_index_list:
-            selected_row = model_index.row()
-            tool_uid = selected_row + 1
-            for key in self.db_tool_dict.keys():
-                if str(key) == str(tool_uid):
-                    selected_tool = self.db_tool_dict[key]
-                    self.on_tool_request(tool=selected_tool)
-
-    def on_cancel_tool(self):
-        for idx in range(self.app.ui.plot_tab_area.count()):
-            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
-                wdg = self.app.ui.plot_tab_area.widget(idx)
-                wdg.deleteLater()
-                self.app.ui.plot_tab_area.removeTab(idx)
-        self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
-
-    def resize_new_tool_table_widget(self, min_size, max_size):
-        """
-        Resize the table widget responsible for adding new tool in the Tool Database
-
-        :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
-        :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
-        :return:
-        """
-        t_height = self.t_height
-        if max_size > min_size:
-            t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
-
-        self.new_tool_table_widget.setMaximumHeight(t_height)
-
-    def closeEvent(self, QCloseEvent):
-        super().closeEvent(QCloseEvent)
-
-
-class ToolsDB2(QtWidgets.QWidget):
-
-    mark_tools_rows = QtCore.pyqtSignal()
-
-    def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
-        super(ToolsDB2, self).__init__(parent)
-
-        self.app = app
-        self.decimals = self.app.decimals
-        self.callback_app = callback_on_edited
-
-        self.on_tool_request = callback_on_tool_request
-
-        self.offset_item_options = ["Path", "In", "Out", "Custom"]
-        self.type_item_options = ["Iso", "Rough", "Finish"]
-        self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
-
-        '''
-        dict to hold all the tools in the Tools DB
-        format:
-        {
-            tool_id: {
-                'name': 'new_tool'
-                'tooldia': self.app.defaults["geometry_cnctooldia"]
-                'offset': 'Path'
-                'offset_value': 0.0
-                'type':  _('Rough'),
-                'tool_type': 'C1'
-                'data': dict()
-            }
-        }
-        '''
-        self.db_tool_dict = {}
-
-        # layouts
-        grid_layout = QtWidgets.QGridLayout()
-        grid_layout.setColumnStretch(0, 0)
-        grid_layout.setColumnStretch(1, 1)
-
-        self.setLayout(grid_layout)
-
-        tree_layout = QtWidgets.QVBoxLayout()
-        grid_layout.addLayout(tree_layout, 0, 0)
-
-        self.tree_widget = FCTree(columns=2, header_hidden=False, protected_column=[0])
-        self.tree_widget.setHeaderLabels(["ID", "Tool Name"])
-        self.tree_widget.setIndentation(0)
-        self.tree_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
-        self.tree_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
-
-        # set alternating colors
-        # self.tree_widget.setAlternatingRowColors(True)
-        # p = QtGui.QPalette()
-        # p.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(226, 237, 253) )
-        # self.tree_widget.setPalette(p)
-
-        self.tree_widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
-        tree_layout.addWidget(self.tree_widget)
-
-        param_hlay = QtWidgets.QHBoxLayout()
-        param_area = QtWidgets.QScrollArea()
-        param_widget = QtWidgets.QWidget()
-        param_widget.setLayout(param_hlay)
-
-        param_area.setWidget(param_widget)
-        param_area.setWidgetResizable(True)
-
-        grid_layout.addWidget(param_area, 0, 1)
-
-        # ###########################################################################
-        # ############## The UI form ################################################
-        # ###########################################################################
-        self.basic_box = QtWidgets.QGroupBox()
-        self.basic_box.setStyleSheet("""
-        QGroupBox
-        {
-            font-size: 16px;
-            font-weight: bold;
-        }
-        """)
-        self.basic_vlay = QtWidgets.QVBoxLayout()
-        self.basic_box.setTitle(_("Basic Geo Parameters"))
-        self.basic_box.setFixedWidth(250)
-
-        self.advanced_box = QtWidgets.QGroupBox()
-        self.advanced_box.setStyleSheet("""
-                QGroupBox
-                {
-                    font-size: 16px;
-                    font-weight: bold;
-                }
-                """)
-        self.advanced_vlay = QtWidgets.QVBoxLayout()
-        self.advanced_box.setTitle(_("Advanced Geo Parameters"))
-        self.advanced_box.setFixedWidth(250)
-
-        self.ncc_box = QtWidgets.QGroupBox()
-        self.ncc_box.setStyleSheet("""
-                        QGroupBox
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.ncc_vlay = QtWidgets.QVBoxLayout()
-        self.ncc_box.setTitle(_("NCC Parameters"))
-        self.ncc_box.setFixedWidth(250)
-
-        self.paint_box = QtWidgets.QGroupBox()
-        self.paint_box.setStyleSheet("""
-                        QGroupBox
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.paint_vlay = QtWidgets.QVBoxLayout()
-        self.paint_box.setTitle(_("Paint Parameters"))
-        self.paint_box.setFixedWidth(250)
-
-        self.basic_box.setLayout(self.basic_vlay)
-        self.advanced_box.setLayout(self.advanced_vlay)
-        self.ncc_box.setLayout(self.ncc_vlay)
-        self.paint_box.setLayout(self.paint_vlay)
-
-        geo_vlay = QtWidgets.QVBoxLayout()
-        geo_vlay.addWidget(self.basic_box)
-        geo_vlay.addWidget(self.advanced_box)
-        geo_vlay.addStretch()
-
-        tools_vlay = QtWidgets.QVBoxLayout()
-        tools_vlay.addWidget(self.ncc_box)
-        tools_vlay.addWidget(self.paint_box)
-        tools_vlay.addStretch()
-
-        param_hlay.addLayout(geo_vlay)
-        param_hlay.addLayout(tools_vlay)
-        param_hlay.addStretch()
-
-        # ###########################################################################
-        # ############### BASIC UI form #############################################
-        # ###########################################################################
-
-        self.grid0 = QtWidgets.QGridLayout()
-        self.basic_vlay.addLayout(self.grid0)
-        self.grid0.setColumnStretch(0, 0)
-        self.grid0.setColumnStretch(1, 1)
-        self.basic_vlay.addStretch()
-
-        # Tool Name
-        self.name_label = QtWidgets.QLabel('<span style="color:red;"><b>%s:</b></span>' % _('Tool Name'))
-        self.name_label.setToolTip(
-            _("Tool name.\n"
-              "This is not used in the app, it's function\n"
-              "is to serve as a note for the user."))
-
-        self.name_entry = FCEntry()
-        self.name_entry.setObjectName('gdb_name')
-
-        self.grid0.addWidget(self.name_label, 0, 0)
-        self.grid0.addWidget(self.name_entry, 0, 1)
-
-        # Tool Dia
-        self.dia_label = QtWidgets.QLabel('%s:' % _('Tool Dia'))
-        self.dia_label.setToolTip(
-            _("Tool Diameter."))
-
-        self.dia_entry = FCDoubleSpinner()
-        self.dia_entry.set_range(-9999.9999, 9999.9999)
-        self.dia_entry.set_precision(self.decimals)
-        self.dia_entry.setObjectName('gdb_dia')
-
-        self.grid0.addWidget(self.dia_label, 1, 0)
-        self.grid0.addWidget(self.dia_entry, 1, 1)
-
-        # Tool Shape
-        self.shape_label = QtWidgets.QLabel('%s:' % _('Tool Shape'))
-        self.shape_label.setToolTip(
-            _("Tool Shape. \n"
-              "Can be:\n"
-              "C1 ... C4 = circular tool with x flutes\n"
-              "B = ball tip milling tool\n"
-              "V = v-shape milling tool"))
-
-        self.shape_combo = FCComboBox()
-        self.shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"])
-        self.shape_combo.setObjectName('gdb_shape')
-
-        self.grid0.addWidget(self.shape_label, 2, 0)
-        self.grid0.addWidget(self.shape_combo, 2, 1)
-
-        # Cut Z
-        self.cutz_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
-        self.cutz_label.setToolTip(
-            _("Cutting Depth.\n"
-              "The depth at which to cut into material."))
-
-        self.cutz_entry = FCDoubleSpinner()
-        self.cutz_entry.set_range(-9999.9999, 9999.9999)
-        self.cutz_entry.set_precision(self.decimals)
-        self.cutz_entry.setObjectName('gdb_cutz')
-
-        self.grid0.addWidget(self.cutz_label, 4, 0)
-        self.grid0.addWidget(self.cutz_entry, 4, 1)
-
-        # Multi Depth
-        self.multidepth_label = QtWidgets.QLabel('%s:' % _("MultiDepth"))
-        self.multidepth_label.setToolTip(
-            _("Multi Depth.\n"
-              "Selecting this will allow cutting in multiple passes,\n"
-              "each pass adding a DPP parameter depth."))
-
-        self.multidepth_cb = FCCheckBox()
-        self.multidepth_cb.setObjectName('gdb_multidepth')
-
-        self.grid0.addWidget(self.multidepth_label, 5, 0)
-        self.grid0.addWidget(self.multidepth_cb, 5, 1)
-
-        # Depth Per Pass
-        self.dpp_label = QtWidgets.QLabel('%s:' % _("DPP"))
-        self.dpp_label.setToolTip(
-            _("DPP. Depth per Pass.\n"
-              "The value used to cut into material on each pass."))
-
-        self.multidepth_entry = FCDoubleSpinner()
-        self.multidepth_entry.set_range(-9999.9999, 9999.9999)
-        self.multidepth_entry.set_precision(self.decimals)
-        self.multidepth_entry.setObjectName('gdb_multidepth_entry')
-
-        self.grid0.addWidget(self.dpp_label, 7, 0)
-        self.grid0.addWidget(self.multidepth_entry, 7, 1)
-
-        # Travel Z
-        self.travelz_label = QtWidgets.QLabel('%s:' % _("Travel Z"))
-        self.travelz_label.setToolTip(
-            _("Clearance Height.\n"
-              "Height at which the milling bit will travel between cuts,\n"
-              "above the surface of the material, avoiding all fixtures."))
-
-        self.travelz_entry = FCDoubleSpinner()
-        self.travelz_entry.set_range(-9999.9999, 9999.9999)
-        self.travelz_entry.set_precision(self.decimals)
-        self.travelz_entry.setObjectName('gdb_travel')
-
-        self.grid0.addWidget(self.travelz_label, 9, 0)
-        self.grid0.addWidget(self.travelz_entry, 9, 1)
-
-        # Feedrate X-Y
-        self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y"))
-        self.frxy_label.setToolTip(
-            _("Feedrate X-Y. Feedrate\n"
-              "The speed on XY plane used while cutting into material."))
-
-        self.frxy_entry = FCDoubleSpinner()
-        self.frxy_entry.set_range(-999999.9999, 999999.9999)
-        self.frxy_entry.set_precision(self.decimals)
-        self.frxy_entry.setObjectName('gdb_frxy')
-
-        self.grid0.addWidget(self.frxy_label, 12, 0)
-        self.grid0.addWidget(self.frxy_entry, 12, 1)
-
-        # Feedrate Z
-        self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z"))
-        self.frz_label.setToolTip(
-            _("Feedrate Z\n"
-              "The speed on Z plane."))
-
-        self.frz_entry = FCDoubleSpinner()
-        self.frz_entry.set_range(-999999.9999, 999999.9999)
-        self.frz_entry.set_precision(self.decimals)
-        self.frz_entry.setObjectName('gdb_frz')
-
-        self.grid0.addWidget(self.frz_label, 14, 0)
-        self.grid0.addWidget(self.frz_entry, 14, 1)
-
-        # Spindle Spped
-        self.spindle_label = QtWidgets.QLabel('%s:' % _("Spindle Speed"))
-        self.spindle_label.setToolTip(
-            _("Spindle Speed.\n"
-              "If it's left empty it will not be used.\n"
-              "The speed of the spindle in RPM."))
-
-        self.spindle_entry = FCDoubleSpinner()
-        self.spindle_entry.set_range(-999999.9999, 999999.9999)
-        self.spindle_entry.set_precision(self.decimals)
-        self.spindle_entry.setObjectName('gdb_spindle')
-
-        self.grid0.addWidget(self.spindle_label, 15, 0)
-        self.grid0.addWidget(self.spindle_entry, 15, 1)
-
-        # Dwell
-        self.dwell_label = QtWidgets.QLabel('%s:' % _("Dwell"))
-        self.dwell_label.setToolTip(
-            _("Dwell.\n"
-              "Check this if a delay is needed to allow\n"
-              "the spindle motor to reach it's set speed."))
-
-        self.dwell_cb = FCCheckBox()
-        self.dwell_cb.setObjectName('gdb_dwell')
-
-        self.grid0.addWidget(self.dwell_label, 16, 0)
-        self.grid0.addWidget(self.dwell_cb, 16, 1)
-
-        # Dwell Time
-        self.dwelltime_label = QtWidgets.QLabel('%s:' % _("Dwelltime"))
-        self.dwelltime_label.setToolTip(
-            _("Dwell Time.\n"
-              "A delay used to allow the motor spindle reach it's set speed."))
-
-        self.dwelltime_entry = FCDoubleSpinner()
-        self.dwelltime_entry.set_range(0.0000, 9999.9999)
-        self.dwelltime_entry.set_precision(self.decimals)
-        self.dwelltime_entry.setObjectName('gdb_dwelltime')
-
-        self.grid0.addWidget(self.dwelltime_label, 17, 0)
-        self.grid0.addWidget(self.dwelltime_entry, 17, 1)
-
-        # ###########################################################################
-        # ############### ADVANCED UI form ##########################################
-        # ###########################################################################
-
-        self.grid1 = QtWidgets.QGridLayout()
-        self.advanced_vlay.addLayout(self.grid1)
-        self.grid1.setColumnStretch(0, 0)
-        self.grid1.setColumnStretch(1, 1)
-        self.advanced_vlay.addStretch()
-
-        # Tool Type
-        self.type_label = QtWidgets.QLabel('%s:' % _("Tool Type"))
-        self.type_label.setToolTip(
-            _("Tool Type.\n"
-              "Can be:\n"
-              "Iso = isolation cut\n"
-              "Rough = rough cut, low feedrate, multiple passes\n"
-              "Finish = finishing cut, high feedrate"))
-
-        self.type_combo = FCComboBox()
-        self.type_combo.addItems(["Iso", "Rough", "Finish"])
-        self.type_combo.setObjectName('gdb_type')
-
-        self.grid1.addWidget(self.type_label, 0, 0)
-        self.grid1.addWidget(self.type_combo, 0, 1)
-
-        # Tool Offset
-        self.tooloffset_label = QtWidgets.QLabel('%s:' % _('Tool Offset'))
-        self.tooloffset_label.setToolTip(
-            _("Tool Offset.\n"
-              "Can be of a few types:\n"
-              "Path = zero offset\n"
-              "In = offset inside by half of tool diameter\n"
-              "Out = offset outside by half of tool diameter\n"
-              "Custom = custom offset using the Custom Offset value"))
-
-        self.tooloffset_combo = FCComboBox()
-        self.tooloffset_combo.addItems(["Path", "In", "Out", "Custom"])
-        self.tooloffset_combo.setObjectName('gdb_tool_offset')
-
-        self.grid1.addWidget(self.tooloffset_label, 2, 0)
-        self.grid1.addWidget(self.tooloffset_combo, 2, 1)
-
-        # Custom Offset
-        self.custom_offset_label = QtWidgets.QLabel('%s:' % _("Custom Offset"))
-        self.custom_offset_label.setToolTip(
-            _("Custom Offset.\n"
-              "A value to be used as offset from the current path."))
-
-        self.custom_offset_entry = FCDoubleSpinner()
-        self.custom_offset_entry.set_range(-9999.9999, 9999.9999)
-        self.custom_offset_entry.set_precision(self.decimals)
-        self.custom_offset_entry.setObjectName('gdb_custom_offset')
-
-        self.grid1.addWidget(self.custom_offset_label, 5, 0)
-        self.grid1.addWidget(self.custom_offset_entry, 5, 1)
-
-        # V-Dia
-        self.vdia_label = QtWidgets.QLabel('%s:' % _("V-Dia"))
-        self.vdia_label.setToolTip(
-            _("V-Dia.\n"
-              "Diameter of the tip for V-Shape Tools."))
-
-        self.vdia_entry = FCDoubleSpinner()
-        self.vdia_entry.set_range(0.0000, 9999.9999)
-        self.vdia_entry.set_precision(self.decimals)
-        self.vdia_entry.setObjectName('gdb_vdia')
-
-        self.grid1.addWidget(self.vdia_label, 7, 0)
-        self.grid1.addWidget(self.vdia_entry, 7, 1)
-
-        # V-Angle
-        self.vangle_label = QtWidgets.QLabel('%s:' % _("V-Angle"))
-        self.vangle_label.setToolTip(
-            _("V-Agle.\n"
-              "Angle at the tip for the V-Shape Tools."))
-
-        self.vangle_entry = FCDoubleSpinner()
-        self.vangle_entry.set_range(-360.0, 360.0)
-        self.vangle_entry.set_precision(self.decimals)
-        self.vangle_entry.setObjectName('gdb_vangle')
-
-        self.grid1.addWidget(self.vangle_label, 8, 0)
-        self.grid1.addWidget(self.vangle_entry, 8, 1)
-
-        # Feedrate Rapids
-        self.frapids_label = QtWidgets.QLabel('%s:' % _("FR Rapids"))
-        self.frapids_label.setToolTip(
-            _("FR Rapids. Feedrate Rapids\n"
-              "Speed used while moving as fast as possible.\n"
-              "This is used only by some devices that can't use\n"
-              "the G0 g-code command. Mostly 3D printers."))
-
-        self.frapids_entry = FCDoubleSpinner()
-        self.frapids_entry.set_range(0.0000, 9999.9999)
-        self.frapids_entry.set_precision(self.decimals)
-        self.frapids_entry.setObjectName('gdb_frapids')
-
-        self.grid1.addWidget(self.frapids_label, 10, 0)
-        self.grid1.addWidget(self.frapids_entry, 10, 1)
-
-        # Extra Cut
-        self.ecut_label = QtWidgets.QLabel('%s:' % _("ExtraCut"))
-        self.ecut_label.setToolTip(
-            _("Extra Cut.\n"
-              "If checked, after a isolation is finished an extra cut\n"
-              "will be added where the start and end of isolation meet\n"
-              "such as that this point is covered by this extra cut to\n"
-              "ensure a complete isolation."))
-
-        self.ecut_cb = FCCheckBox()
-        self.ecut_cb.setObjectName('gdb_ecut')
-
-        self.grid1.addWidget(self.ecut_label, 12, 0)
-        self.grid1.addWidget(self.ecut_cb, 12, 1)
-
-        # Extra Cut Length
-        self.ecut_length_label = QtWidgets.QLabel('%s:' % _("E-Cut Length"))
-        self.ecut_length_label.setToolTip(
-            _("Extra Cut length.\n"
-              "If checked, after a isolation is finished an extra cut\n"
-              "will be added where the start and end of isolation meet\n"
-              "such as that this point is covered by this extra cut to\n"
-              "ensure a complete isolation. This is the length of\n"
-              "the extra cut."))
-
-        self.ecut_length_entry = FCDoubleSpinner()
-        self.ecut_length_entry.set_range(0.0000, 9999.9999)
-        self.ecut_length_entry.set_precision(self.decimals)
-        self.ecut_length_entry.setObjectName('gdb_ecut_length')
-
-        self.grid1.addWidget(self.ecut_length_label, 13, 0)
-        self.grid1.addWidget(self.ecut_length_entry, 13, 1)
-
-        # ###########################################################################
-        # ############### NCC UI form ###############################################
-        # ###########################################################################
-
-        self.grid2 = QtWidgets.QGridLayout()
-        self.ncc_vlay.addLayout(self.grid2)
-        self.grid2.setColumnStretch(0, 0)
-        self.grid2.setColumnStretch(1, 1)
-        self.ncc_vlay.addStretch()
-
-        # Operation
-        op_label = QtWidgets.QLabel('%s:' % _('Operation'))
-        op_label.setToolTip(
-            _("The 'Operation' can be:\n"
-              "- Isolation -> will ensure that the non-copper clearing is always complete.\n"
-              "If it's not successful then the non-copper clearing will fail, too.\n"
-              "- Clear -> the regular non-copper clearing.")
-        )
-
-        self.op_radio = RadioSet([
-            {"label": _("Clear"), "value": "clear"},
-            {"label": _("Isolation"), "value": "iso"}
-        ], orientation='horizontal', stretch=False)
-        self.op_radio.setObjectName("gdb_n_operation")
-
-        self.grid2.addWidget(op_label, 13, 0)
-        self.grid2.addWidget(self.op_radio, 13, 1)
-
-        # Milling Type Radio Button
-        self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
-        self.milling_type_label.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-
-        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conventional'), 'value': 'cv'}])
-        self.milling_type_radio.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-        self.milling_type_radio.setObjectName("gdb_n_milling_type")
-
-        self.grid2.addWidget(self.milling_type_label, 14, 0)
-        self.grid2.addWidget(self.milling_type_radio, 14, 1)
-
-        # Overlap Entry
-        nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
-        nccoverlabel.setToolTip(
-            _("How much (percentage) of the tool width to overlap each tool pass.\n"
-              "Adjust the value starting with lower values\n"
-              "and increasing it if areas that should be cleared are still \n"
-              "not cleared.\n"
-              "Lower values = faster processing, faster execution on CNC.\n"
-              "Higher values = slow processing and slow execution on CNC\n"
-              "due of too many paths.")
-        )
-        self.ncc_overlap_entry = FCDoubleSpinner(suffix='%')
-        self.ncc_overlap_entry.set_precision(self.decimals)
-        self.ncc_overlap_entry.setWrapping(True)
-        self.ncc_overlap_entry.setRange(0.000, 99.9999)
-        self.ncc_overlap_entry.setSingleStep(0.1)
-        self.ncc_overlap_entry.setObjectName("gdb_n_overlap")
-
-        self.grid2.addWidget(nccoverlabel, 15, 0)
-        self.grid2.addWidget(self.ncc_overlap_entry, 15, 1)
-
-        # Margin
-        nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
-        nccmarginlabel.setToolTip(
-            _("Bounding box margin.")
-        )
-        self.ncc_margin_entry = FCDoubleSpinner()
-        self.ncc_margin_entry.set_precision(self.decimals)
-        self.ncc_margin_entry.set_range(-9999.9999, 9999.9999)
-        self.ncc_margin_entry.setObjectName("gdb_n_margin")
-
-        self.grid2.addWidget(nccmarginlabel, 16, 0)
-        self.grid2.addWidget(self.ncc_margin_entry, 16, 1)
-
-        # Method
-        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
-        methodlabel.setToolTip(
-            _("Algorithm for copper clearing:\n"
-              "- Standard: Fixed step inwards.\n"
-              "- Seed-based: Outwards from seed.\n"
-              "- Line-based: Parallel lines.")
-        )
-
-        self.ncc_method_combo = FCComboBox()
-        self.ncc_method_combo.addItems(
-            [_("Standard"), _("Seed"), _("Lines")]
-        )
-        self.ncc_method_combo.setObjectName("gdb_n_method")
-
-        self.grid2.addWidget(methodlabel, 17, 0)
-        self.grid2.addWidget(self.ncc_method_combo, 17, 1)
-
-        # Connect lines
-        self.ncc_connect_cb = FCCheckBox('%s' % _("Connect"))
-        self.ncc_connect_cb.setObjectName("gdb_n_connect")
-
-        self.ncc_connect_cb.setToolTip(
-            _("Draw lines between resulting\n"
-              "segments to minimize tool lifts.")
-        )
-        self.grid2.addWidget(self.ncc_connect_cb, 18, 0)
-
-        # Contour
-        self.ncc_contour_cb = FCCheckBox('%s' % _("Contour"))
-        self.ncc_contour_cb.setObjectName("gdb_n_contour")
-
-        self.ncc_contour_cb.setToolTip(
-            _("Cut around the perimeter of the polygon\n"
-              "to trim rough edges.")
-        )
-        self.grid2.addWidget(self.ncc_contour_cb, 18, 1)
-
-        # ## NCC Offset choice
-        self.ncc_choice_offset_cb = FCCheckBox('%s' % _("Offset"))
-        self.ncc_choice_offset_cb.setObjectName("gdb_n_offset")
-
-        self.ncc_choice_offset_cb.setToolTip(
-            _("If used, it will add an offset to the copper features.\n"
-              "The copper clearing will finish to a distance\n"
-              "from the copper features.\n"
-              "The value can be between 0 and 10 FlatCAM units.")
-        )
-        self.grid2.addWidget(self.ncc_choice_offset_cb, 19, 0)
-
-        # ## NCC Offset Entry
-        self.ncc_offset_spinner = FCDoubleSpinner()
-        self.ncc_offset_spinner.set_range(0.00, 10.00)
-        self.ncc_offset_spinner.set_precision(4)
-        self.ncc_offset_spinner.setWrapping(True)
-        self.ncc_offset_spinner.setObjectName("gdb_n_offset_value")
-
-        units = self.app.defaults['units'].upper()
-        if units == 'MM':
-            self.ncc_offset_spinner.setSingleStep(0.1)
-        else:
-            self.ncc_offset_spinner.setSingleStep(0.01)
-
-        self.grid2.addWidget(self.ncc_offset_spinner, 19, 1)
-
-        # ###########################################################################
-        # ############### Paint UI form #############################################
-        # ###########################################################################
-
-        self.grid3 = QtWidgets.QGridLayout()
-        self.paint_vlay.addLayout(self.grid3)
-        self.grid3.setColumnStretch(0, 0)
-        self.grid3.setColumnStretch(1, 1)
-        self.paint_vlay.addStretch()
-
-        # Overlap
-        ovlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
-        ovlabel.setToolTip(
-            _("How much (percentage) of the tool width to overlap each tool pass.\n"
-              "Adjust the value starting with lower values\n"
-              "and increasing it if areas that should be painted are still \n"
-              "not painted.\n"
-              "Lower values = faster processing, faster execution on CNC.\n"
-              "Higher values = slow processing and slow execution on CNC\n"
-              "due of too many paths.")
-        )
-        self.paintoverlap_entry = FCDoubleSpinner(suffix='%')
-        self.paintoverlap_entry.set_precision(3)
-        self.paintoverlap_entry.setWrapping(True)
-        self.paintoverlap_entry.setRange(0.0000, 99.9999)
-        self.paintoverlap_entry.setSingleStep(0.1)
-        self.paintoverlap_entry.setObjectName('gdb_p_overlap')
-
-        self.grid3.addWidget(ovlabel, 1, 0)
-        self.grid3.addWidget(self.paintoverlap_entry, 1, 1)
-
-        # Margin
-        marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
-        marginlabel.setToolTip(
-            _("Distance by which to avoid\n"
-              "the edges of the polygon to\n"
-              "be painted.")
-        )
-        self.paintmargin_entry = FCDoubleSpinner()
-        self.paintmargin_entry.set_precision(self.decimals)
-        self.paintmargin_entry.set_range(-9999.9999, 9999.9999)
-        self.paintmargin_entry.setObjectName('gdb_p_margin')
-
-        self.grid3.addWidget(marginlabel, 2, 0)
-        self.grid3.addWidget(self.paintmargin_entry, 2, 1)
-
-        # Method
-        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
-        methodlabel.setToolTip(
-            _("Algorithm for painting:\n"
-              "- Standard: Fixed step inwards.\n"
-              "- Seed-based: Outwards from seed.\n"
-              "- Line-based: Parallel lines.\n"
-              "- Laser-lines: Active only for Gerber objects.\n"
-              "Will create lines that follow the traces.\n"
-              "- Combo: In case of failure a new method will be picked from the above\n"
-              "in the order specified.")
-        )
-
-        self.paintmethod_combo = FCComboBox()
-        self.paintmethod_combo.addItems(
-            [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")]
-        )
-        idx = self.paintmethod_combo.findText(_("Laser_lines"))
-        self.paintmethod_combo.model().item(idx).setEnabled(False)
-
-        self.paintmethod_combo.setObjectName('gdb_p_method')
-
-        self.grid3.addWidget(methodlabel, 7, 0)
-        self.grid3.addWidget(self.paintmethod_combo, 7, 1)
-
-        # Connect lines
-        self.pathconnect_cb = FCCheckBox('%s' % _("Connect"))
-        self.pathconnect_cb.setObjectName('gdb_p_connect')
-        self.pathconnect_cb.setToolTip(
-            _("Draw lines between resulting\n"
-              "segments to minimize tool lifts.")
-        )
-
-        self.paintcontour_cb = FCCheckBox('%s' % _("Contour"))
-        self.paintcontour_cb.setObjectName('gdb_p_contour')
-        self.paintcontour_cb.setToolTip(
-            _("Cut around the perimeter of the polygon\n"
-              "to trim rough edges.")
-        )
-
-        self.grid3.addWidget(self.pathconnect_cb, 10, 0)
-        self.grid3.addWidget(self.paintcontour_cb, 10, 1)
-
-        # ####################################################################
-        # ####################################################################
-        # GUI for the lower part of the window
-        # ####################################################################
-        # ####################################################################
-
-        new_vlay = QtWidgets.QVBoxLayout()
-        grid_layout.addLayout(new_vlay, 1, 0, 1, 2)
-
-        self.buttons_frame = QtWidgets.QFrame()
-        self.buttons_frame.setContentsMargins(0, 0, 0, 0)
-        new_vlay.addWidget(self.buttons_frame)
-        self.buttons_box = QtWidgets.QHBoxLayout()
-        self.buttons_box.setContentsMargins(0, 0, 0, 0)
-        self.buttons_frame.setLayout(self.buttons_box)
-        self.buttons_frame.show()
-
-        add_entry_btn = FCButton(_("Add Tool in DB"))
-        add_entry_btn.setToolTip(
-            _("Add a new tool in the Tools Database.\n"
-              "It will be used in the Geometry UI.\n"
-              "You can edit it after it is added.")
-        )
-        self.buttons_box.addWidget(add_entry_btn)
-
-        # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
-        # add_fct_entry_btn.setToolTip(
-        #     _("Add a new tool in the Tools Database.\n"
-        #       "It will be used in the Paint/NCC Tools UI.\n"
-        #       "You can edit it after it is added.")
-        # )
-        # self.buttons_box.addWidget(add_fct_entry_btn)
-
-        remove_entry_btn = FCButton(_("Delete Tool from DB"))
-        remove_entry_btn.setToolTip(
-            _("Remove a selection of tools in the Tools Database.")
-        )
-        self.buttons_box.addWidget(remove_entry_btn)
-
-        export_db_btn = FCButton(_("Export DB"))
-        export_db_btn.setToolTip(
-            _("Save the Tools Database to a custom text file.")
-        )
-        self.buttons_box.addWidget(export_db_btn)
-
-        import_db_btn = FCButton(_("Import DB"))
-        import_db_btn.setToolTip(
-            _("Load the Tools Database information's from a custom text file.")
-        )
-        self.buttons_box.addWidget(import_db_btn)
-
-        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
-        self.add_tool_from_db.setToolTip(
-            _("Add a new tool in the Tools Table of the\n"
-              "active Geometry object after selecting a tool\n"
-              "in the Tools Database.")
-        )
-        self.add_tool_from_db.hide()
-
-        self.cancel_tool_from_db = FCButton(_("Cancel"))
-        self.cancel_tool_from_db.hide()
-
-        hlay = QtWidgets.QHBoxLayout()
-        tree_layout.addLayout(hlay)
-        hlay.addWidget(self.add_tool_from_db)
-        hlay.addWidget(self.cancel_tool_from_db)
-        hlay.addStretch()
-
-        # ##############################################################################
-        # ##############################################################################
-        # ########## SETUP THE DICTIONARIES THAT HOLD THE WIDGETS #####################
-        # ##############################################################################
-        # ##############################################################################
-
-        self.form_fields = {
-            # Basic
-            "name":             self.name_entry,
-            "tooldia":          self.dia_entry,
-            "tool_type":        self.shape_combo,
-            "cutz":             self.cutz_entry,
-            "multidepth":       self.multidepth_cb,
-            "depthperpass":     self.multidepth_entry,
-            "travelz":          self.travelz_entry,
-            "feedrate":         self.frxy_entry,
-            "feedrate_z":       self.frz_entry,
-            "spindlespeed":     self.spindle_entry,
-            "dwell":            self.dwell_cb,
-            "dwelltime":        self.dwelltime_entry,
-
-            # Advanced
-            "type":             self.type_combo,
-            "offset":           self.tooloffset_combo,
-            "offset_value":     self.custom_offset_entry,
-            "vtipdia":          self.vdia_entry,
-            "vtipangle":        self.vangle_entry,
-            "feedrate_rapid":   self.frapids_entry,
-            "extracut":         self.ecut_cb,
-            "extracut_length":  self.ecut_length_entry,
-
-            # NCC
-            "tools_nccoperation":       self.op_radio,
-            "tools_nccmilling_type":    self.milling_type_radio,
-            "tools_nccoverlap":         self.ncc_overlap_entry,
-            "tools_nccmargin":          self.ncc_margin_entry,
-            "tools_nccmethod":          self.ncc_method_combo,
-            "tools_nccconnect":         self.ncc_connect_cb,
-            "tools_ncccontour":         self.ncc_contour_cb,
-            "tools_ncc_offset_choice":  self.ncc_choice_offset_cb,
-            "tools_ncc_offset_value":   self.ncc_offset_spinner,
-
-            # Paint
-            "tools_paintoverlap":       self.paintoverlap_entry,
-            "tools_paintmargin":        self.paintmargin_entry,
-            "tools_paintmethod":        self.paintmethod_combo,
-            "tools_pathconnect":        self.pathconnect_cb,
-            "tools_paintcontour":       self.paintcontour_cb,
-        }
-
-        self.name2option = {
-            # Basic
-            "gdb_name":             "name",
-            "gdb_dia":              "tooldia",
-            "gdb_shape":            "tool_type",
-            "gdb_cutz":             "cutz",
-            "gdb_multidepth":       "multidepth",
-            "gdb_multidepth_entry": "depthperpass",
-            "gdb_travel":           "travelz",
-            "gdb_frxy":             "feedrate",
-            "gdb_frz":              "feedrate_z",
-            "gdb_spindle":          "spindlespeed",
-            "gdb_dwell":            "dwell",
-            "gdb_dwelltime":        "dwelltime",
-
-            # Advanced
-            "gdb_type":             "type",
-            "gdb_tool_offset":      "offset",
-            "gdb_custom_offset":    "offset_value",
-            "gdb_vdia":             "vtipdia",
-            "gdb_vangle":           "vtipangle",
-            "gdb_frapids":          "feedrate_rapid",
-            "gdb_ecut":             "extracut",
-            "gdb_ecut_length":      "extracut_length",
-
-            # NCC
-            "gdb_n_operation":      "tools_nccoperation",
-            "gdb_n_overlap":        "tools_nccoverlap",
-            "gdb_n_margin":         "tools_nccmargin",
-            "gdb_n_method":         "tools_nccmethod",
-            "gdb_n_connect":        "tools_nccconnect",
-            "gdb_n_contour":        "tools_ncccontour",
-            "gdb_n_offset":         "tools_ncc_offset_choice",
-            "gdb_n_offset_value":   "tools_ncc_offset_value",
-            "gdb_n_milling_type":   "tools_nccmilling_type",
-
-            # Paint
-            'gdb_p_overlap':        "tools_paintoverlap",
-            'gdb_p_margin':         "tools_paintmargin",
-            'gdb_p_method':         "tools_paintmethod",
-            'gdb_p_connect':        "tools_pathconnect",
-            'gdb_p_contour':        "tools_paintcontour",
-        }
-
-        self.current_toolid = None
-
-        # variable to show if double clicking and item will trigger adding a tool from DB
-        self.ok_to_add = False
-
-        # ##############################################################################
-        # ######################## SIGNALS #############################################
-        # ##############################################################################
-
-        add_entry_btn.clicked.connect(self.on_tool_add)
-        remove_entry_btn.clicked.connect(self.on_tool_delete)
-        export_db_btn.clicked.connect(self.on_export_tools_db_file)
-        import_db_btn.clicked.connect(self.on_import_tools_db_file)
-        # closebtn.clicked.connect(self.accept)
-
-        self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
-        self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
-
-        # self.tree_widget.selectionModel().selectionChanged.connect(self.on_list_selection_change)
-        self.tree_widget.currentItemChanged.connect(self.on_list_selection_change)
-        self.tree_widget.itemChanged.connect(self.on_list_item_edited)
-        self.tree_widget.customContextMenuRequested.connect(self.on_menu_request)
-
-        self.tree_widget.itemDoubleClicked.connect(self.on_item_double_clicked)
-
-        self.setup_db_ui()
-
-    def on_menu_request(self, pos):
-
-        menu = QtWidgets.QMenu()
-        add_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/plus16.png'), _("Add to DB"))
-        add_tool.triggered.connect(self.on_tool_add)
-
-        copy_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/copy16.png'), _("Copy from DB"))
-        copy_tool.triggered.connect(self.on_tool_copy)
-
-        delete_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete from DB"))
-        delete_tool.triggered.connect(self.on_tool_delete)
-
-        # tree_item = self.tree_widget.itemAt(pos)
-        menu.exec(self.tree_widget.viewport().mapToGlobal(pos))
-
-    def on_item_double_clicked(self, item, column):
-        if column == 0 and self.ok_to_add is True:
-            self.ok_to_add = False
-            self.on_tool_requested_from_app()
-
-    def on_list_selection_change(self, current, previous):
-        # for idx in current.indexes():
-        #     print(idx.data())
-        # print(current.text(0))
-        self.current_toolid = int(current.text(0))
-
-        self.storage_to_form(self.db_tool_dict[current.text(0)])
-
-    def on_list_item_edited(self, item, column):
-        if column == 0:
-            return
-
-        self.name_entry.set_value(item.text(1))
-
-    def storage_to_form(self, dict_storage):
-        for form_key in self.form_fields:
-            for storage_key in dict_storage:
-                if form_key == storage_key:
-                    try:
-                        self.form_fields[form_key].set_value(dict_storage[form_key])
-                    except Exception as e:
-                        print(str(e))
-                if storage_key == 'data':
-                    for data_key in dict_storage[storage_key]:
-                        if form_key == data_key:
-                            try:
-                                self.form_fields[form_key].set_value(dict_storage['data'][data_key])
-                            except Exception as e:
-                                print(str(e))
-
-    def form_to_storage(self, tool):
-        self.blockSignals(True)
-
-        widget_changed = self.sender()
-        wdg_objname = widget_changed.objectName()
-        option_changed = self.name2option[wdg_objname]
-
-        tooluid_item = int(tool)
-
-        for tooluid_key, tooluid_val in self.db_tool_dict.items():
-            if int(tooluid_key) == tooluid_item:
-                new_option_value = self.form_fields[option_changed].get_value()
-                if option_changed in tooluid_val:
-                    tooluid_val[option_changed] = new_option_value
-                if option_changed in tooluid_val['data']:
-                    tooluid_val['data'][option_changed] = new_option_value
-
-        self.blockSignals(False)
-
-    def setup_db_ui(self):
-        filename = self.app.data_path + '/geo_tools_db.FlatDB'
-
-        # load the database tools from the file
-        try:
-            with open(filename) as f:
-                tools = f.read()
-        except IOError:
-            self.app.log.error("Could not load tools DB file.")
-            self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
-            return
-
-        try:
-            self.db_tool_dict = json.loads(tools)
-        except Exception:
-            e = sys.exc_info()[0]
-            self.app.log.error(str(e))
-            self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
-            return
-
-        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
-
-        self.build_db_ui()
-
-    def build_db_ui(self):
-        self.ui_disconnect()
-        nr_crt = 0
-
-        parent = self.tree_widget
-        self.tree_widget.blockSignals(True)
-        self.tree_widget.clear()
-        self.tree_widget.blockSignals(False)
-
-        for toolid, dict_val in self.db_tool_dict.items():
-            row = nr_crt
-            nr_crt += 1
-
-            t_name = dict_val['name']
-            try:
-                # self.add_tool_table_line(row, name=t_name, tooldict=dict_val)
-                self.tree_widget.blockSignals(True)
-                try:
-                    self.tree_widget.addParentEditable(parent=parent, title=[str(row+1), t_name], editable=True)
-                except Exception as e:
-                    print('FlatCAMCoomn.ToolDB2.build_db_ui() -> ', str(e))
-                self.tree_widget.blockSignals(False)
-            except Exception as e:
-                self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
-
-        if self.current_toolid is None or self.current_toolid < 1:
-            if self.db_tool_dict:
-                self.storage_to_form(self.db_tool_dict['1'])
-
-                # Enable GUI
-                self.basic_box.setEnabled(True)
-                self.advanced_box.setEnabled(True)
-                self.ncc_box.setEnabled(True)
-                self.paint_box.setEnabled(True)
-
-                self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0))
-                # self.tree_widget.setFocus()
-
-            else:
-                # Disable GUI
-                self.basic_box.setEnabled(False)
-                self.advanced_box.setEnabled(False)
-                self.ncc_box.setEnabled(False)
-                self.paint_box.setEnabled(False)
-        else:
-            self.storage_to_form(self.db_tool_dict[str(self.current_toolid)])
-
-        self.ui_connect()
-
-    def on_tool_add(self):
-        """
-        Add a tool in the DB Tool Table
-        :return: None
-        """
-
-        default_data = {}
-        default_data.update({
-            "plot":             True,
-            "cutz":             float(self.app.defaults["geometry_cutz"]),
-            "multidepth":       self.app.defaults["geometry_multidepth"],
-            "depthperpass":     float(self.app.defaults["geometry_depthperpass"]),
-            "vtipdia":          float(self.app.defaults["geometry_vtipdia"]),
-            "vtipangle":        float(self.app.defaults["geometry_vtipangle"]),
-            "travelz":          float(self.app.defaults["geometry_travelz"]),
-            "feedrate":         float(self.app.defaults["geometry_feedrate"]),
-            "feedrate_z":       float(self.app.defaults["geometry_feedrate_z"]),
-            "feedrate_rapid":   float(self.app.defaults["geometry_feedrate_rapid"]),
-            "spindlespeed":     self.app.defaults["geometry_spindlespeed"],
-            "dwell":            self.app.defaults["geometry_dwell"],
-            "dwelltime":        float(self.app.defaults["geometry_dwelltime"]),
-            "ppname_g":         self.app.defaults["geometry_ppname_g"],
-            "extracut":         self.app.defaults["geometry_extracut"],
-            "extracut_length":  float(self.app.defaults["geometry_extracut_length"]),
-            "toolchange":       self.app.defaults["geometry_toolchange"],
-            "toolchangexy":     self.app.defaults["geometry_toolchangexy"],
-            "toolchangez":      float(self.app.defaults["geometry_toolchangez"]),
-            "startz":           self.app.defaults["geometry_startz"],
-            "endz":             float(self.app.defaults["geometry_endz"]),
-
-            # NCC
-            "tools_nccoperation":       self.app.defaults["tools_nccoperation"],
-            "tools_nccmilling_type":    self.app.defaults["tools_nccmilling_type"],
-            "tools_nccoverlap":         float(self.app.defaults["tools_nccoverlap"]),
-            "tools_nccmargin":          float(self.app.defaults["tools_nccmargin"]),
-            "tools_nccmethod":          self.app.defaults["tools_nccmethod"],
-            "tools_nccconnect":         self.app.defaults["tools_nccconnect"],
-            "tools_ncccontour":         self.app.defaults["tools_ncccontour"],
-            "tools_ncc_offset_choice":  self.app.defaults["tools_ncc_offset_choice"],
-            "tools_ncc_offset_value":   float(self.app.defaults["tools_ncc_offset_value"]),
-
-            # Paint
-            "tools_paintoverlap":       float(self.app.defaults["tools_paintoverlap"]),
-            "tools_paintmargin":        float(self.app.defaults["tools_paintmargin"]),
-            "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
-            "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
-            "tools_paintcontour":       self.app.defaults["tools_paintcontour"],
-        })
-
-        dict_elem = {}
-        dict_elem['name'] = 'new_tool'
-        if type(self.app.defaults["geometry_cnctooldia"]) == float:
-            dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
-        else:
-            try:
-                tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
-                tools_diameters = [eval(a) for a in tools_string if a != '']
-                dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
-            except Exception as e:
-                self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
-                return
-
-        dict_elem['offset'] = 'Path'
-        dict_elem['offset_value'] = 0.0
-        dict_elem['type'] = 'Rough'
-        dict_elem['tool_type'] = 'C1'
-        dict_elem['data'] = default_data
-
-        new_toolid = len(self.db_tool_dict) + 1
-        self.db_tool_dict[str(new_toolid)] = deepcopy(dict_elem)
-
-        # add the new entry to the Tools DB table
-        self.update_storage()
-        self.build_db_ui()
-        self.app.inform.emit('[success] %s' % _("Tool added to DB."))
-
-    def on_tool_copy(self):
-        """
-        Copy a selection of Tools in the Tools DB table
-        :return:
-        """
-        new_tool_id = len(self.db_tool_dict)
-        for item in self.tree_widget.selectedItems():
-            old_tool_id = item.data(0, QtCore.Qt.DisplayRole)
-
-            for toolid, dict_val in list(self.db_tool_dict.items()):
-                if int(old_tool_id) == int(toolid):
-                    new_tool_id += 1
-                    new_key = str(new_tool_id)
-
-                    self.db_tool_dict.update({
-                        new_key: deepcopy(dict_val)
-                    })
-
-        self.current_toolid = new_tool_id
-
-        self.update_storage()
-        self.build_db_ui()
-        self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
-
-    def on_tool_delete(self):
-        """
-        Delete a selection of Tools in the Tools DB table
-        :return:
-        """
-        for item in self.tree_widget.selectedItems():
-            toolname_to_remove = item.data(0, QtCore.Qt.DisplayRole)
-
-            for toolid, dict_val in list(self.db_tool_dict.items()):
-                if int(toolname_to_remove) == int(toolid):
-                    # remove from the storage
-                    self.db_tool_dict.pop(toolid, None)
-
-        self.current_toolid -= 1
-
-        self.update_storage()
-        self.build_db_ui()
-        self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
-
-    def on_export_tools_db_file(self):
-        self.app.report_usage("on_export_tools_db_file")
-        self.app.log.debug("on_export_tools_db_file()")
-
-        date = str(datetime.today()).rpartition('.')[0]
-        date = ''.join(c for c in date if c not in ':-')
-        date = date.replace(' ', '_')
-
-        filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = FCFileSaveDialog.get_saved_filename( caption=_("Export Tools Database"),
-                                                             directory='{l_save}/FlatCAM_{n}_{date}'.format(
-                                                                 l_save=str(self.app.get_last_save_folder()),
-                                                                 n=_("Tools_Database"),
-                                                                 date=date),
-                                                             filter=filter__)
-
-        filename = str(filename)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-            return
-        else:
-            try:
-                f = open(filename, 'w')
-                f.close()
-            except PermissionError:
-                self.app.inform.emit('[WARNING] %s' %
-                                     _("Permission denied, saving not possible.\n"
-                                       "Most likely another app is holding the file open and not accessible."))
-                return
-            except IOError:
-                self.app.log.debug('Creating a new Tools DB file ...')
-                f = open(filename, 'w')
-                f.close()
-            except Exception:
-                e = sys.exc_info()[0]
-                self.app.log.error("Could not load Tools DB file.")
-                self.app.log.error(str(e))
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
-                return
-
-            # Save update options
-            try:
-                # Save Tools DB in a file
-                try:
-                    with open(filename, "w") as f:
-                        json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
-                except Exception as e:
-                    self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
-                    self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                    return
-            except Exception:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                return
-
-        self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
-
-    def on_import_tools_db_file(self):
-        self.app.report_usage("on_import_tools_db_file")
-        self.app.log.debug("on_import_tools_db_file()")
-
-        filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
-
-        if filename == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-        else:
-            try:
-                with open(filename) as f:
-                    tools_in_db = f.read()
-            except IOError:
-                self.app.log.error("Could not load Tools DB file.")
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
-                return
-
-            try:
-                self.db_tool_dict = json.loads(tools_in_db)
-            except Exception:
-                e = sys.exc_info()[0]
-                self.app.log.error(str(e))
-                self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
-                return
-
-            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
-            self.build_db_ui()
-            self.update_storage()
-
-    def on_save_tools_db(self, silent=False):
-        self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.")
-
-        filename = self.app.data_path + "/geo_tools_db.FlatDB"
-
-        # Preferences save, update the color of the Tools DB Tab text
-        for idx in range(self.app.ui.plot_tab_area.count()):
-            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
-                self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
-
-                # Save Tools DB in a file
-                try:
-                    f = open(filename, "w")
-                    json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
-                    f.close()
-                except Exception as e:
-                    self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
-                    return
-
-                if not silent:
-                    self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
-
-    def ui_connect(self):
-        # make sure that we don't make multiple connections to the widgets
-        self.ui_disconnect()
-
-        self.name_entry.editingFinished.connect(self.update_tree_name)
-
-        for key in self.form_fields:
-            wdg = self.form_fields[key]
-
-            # FCEntry
-            if isinstance(wdg, FCEntry):
-                wdg.textChanged.connect(self.update_storage)
-
-            # ComboBox
-            if isinstance(wdg, FCComboBox):
-                wdg.currentIndexChanged.connect(self.update_storage)
-
-            # CheckBox
-            if isinstance(wdg, FCCheckBox):
-                wdg.toggled.connect(self.update_storage)
-
-            # FCRadio
-            if isinstance(wdg, RadioSet):
-                wdg.activated_custom.connect(self.update_storage)
-
-            # SpinBox, DoubleSpinBox
-            if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
-                wdg.valueChanged.connect(self.update_storage)
-
-    def ui_disconnect(self):
-        try:
-            self.name_entry.editingFinished.disconnect(self.update_tree_name)
-        except (TypeError, AttributeError):
-            pass
-
-        for key in self.form_fields:
-            wdg = self.form_fields[key]
-
-            # FCEntry
-            if isinstance(wdg, FCEntry):
-                try:
-                    wdg.textChanged.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-            # ComboBox
-            if isinstance(wdg, FCComboBox):
-                try:
-                    wdg.currentIndexChanged.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-            # CheckBox
-            if isinstance(wdg, FCCheckBox):
-                try:
-                    wdg.toggled.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-            # FCRadio
-            if isinstance(wdg, RadioSet):
-                try:
-                    wdg.activated_custom.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-            # SpinBox, DoubleSpinBox
-            if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
-                try:
-                    wdg.valueChanged.disconnect(self.update_storage)
-                except (TypeError, AttributeError):
-                    pass
-
-    def update_tree_name(self):
-        val = self.name_entry.get_value()
-
-        item = self.tree_widget.currentItem()
-        # I'm setting the value for the second column (designated by 1) because first column holds the ID
-        # and second column holds the Name (this behavior is set in the build_ui method)
-        item.setData(1, QtCore.Qt.DisplayRole, val)
-
-    def update_storage(self):
-        """
-        Update the dictionary that is the storage of the tools 'database'
-        :return:
-        """
-        tool_id = str(self.current_toolid)
-
-        wdg = self.sender()
-        if wdg is None:
-            return
-
-        wdg_name = wdg.objectName()
-
-        try:
-            val = wdg.get_value()
-        except AttributeError:
-            return
-
-        if wdg_name == "gdb_name":
-            self.db_tool_dict[tool_id]['name'] = val
-        elif wdg_name == "gdb_dia":
-            self.db_tool_dict[tool_id]['tooldia'] = val
-        elif wdg_name == "gdb_tool_offset":
-            self.db_tool_dict[tool_id]['offset'] = val
-        elif wdg_name == "gdb_custom_offset":
-            self.db_tool_dict[tool_id]['offset_value'] = val
-        elif wdg_name == "gdb_type":
-            self.db_tool_dict[tool_id]['type'] = val
-        elif wdg_name == "gdb_shape":
-            self.db_tool_dict[tool_id]['tool_type'] = val
-        else:
-            if wdg_name == "gdb_cutz":
-                self.db_tool_dict[tool_id]['data']['cutz'] = val
-            elif wdg_name == "gdb_multidepth":
-                self.db_tool_dict[tool_id]['data']['multidepth'] = val
-            elif wdg_name == "gdb_multidepth_entry":
-                self.db_tool_dict[tool_id]['data']['depthperpass'] = val
-
-            elif wdg_name == "gdb_travel":
-                self.db_tool_dict[tool_id]['data']['travelz'] = val
-            elif wdg_name == "gdb_frxy":
-                self.db_tool_dict[tool_id]['data']['feedrate'] = val
-            elif wdg_name == "gdb_frz":
-                self.db_tool_dict[tool_id]['data']['feedrate_z'] = val
-            elif wdg_name == "gdb_spindle":
-                self.db_tool_dict[tool_id]['data']['spindlespeed'] = val
-            elif wdg_name == "gdb_dwell":
-                self.db_tool_dict[tool_id]['data']['dwell'] = val
-            elif wdg_name == "gdb_dwelltime":
-                self.db_tool_dict[tool_id]['data']['dwelltime'] = val
-
-            elif wdg_name == "gdb_vdia":
-                self.db_tool_dict[tool_id]['data']['vtipdia'] = val
-            elif wdg_name == "gdb_vangle":
-                self.db_tool_dict[tool_id]['data']['vtipangle'] = val
-            elif wdg_name == "gdb_frapids":
-                self.db_tool_dict[tool_id]['data']['feedrate_rapid'] = val
-            elif wdg_name == "gdb_ecut":
-                self.db_tool_dict[tool_id]['data']['extracut'] = val
-            elif wdg_name == "gdb_ecut_length":
-                self.db_tool_dict[tool_id]['data']['extracut_length'] = val
-
-            # NCC Tool
-            elif wdg_name == "gdb_n_operation":
-                self.db_tool_dict[tool_id]['data']['tools_nccoperation'] = val
-            elif wdg_name == "gdb_n_overlap":
-                self.db_tool_dict[tool_id]['data']['tools_nccoverlap'] = val
-            elif wdg_name == "gdb_n_margin":
-                self.db_tool_dict[tool_id]['data']['tools_nccmargin'] = val
-            elif wdg_name == "gdb_n_method":
-                self.db_tool_dict[tool_id]['data']['tools_nccmethod'] = val
-            elif wdg_name == "gdb_n_connect":
-                self.db_tool_dict[tool_id]['data']['tools_nccconnect'] = val
-            elif wdg_name == "gdb_n_contour":
-                self.db_tool_dict[tool_id]['data']['tools_ncccontour'] = val
-            elif wdg_name == "gdb_n_offset":
-                self.db_tool_dict[tool_id]['data']['tools_ncc_offset_choice'] = val
-            elif wdg_name == "gdb_n_offset_value":
-                self.db_tool_dict[tool_id]['data']['tools_ncc_offset_value'] = val
-            elif wdg_name == "gdb_n_milling_type":
-                self.db_tool_dict[tool_id]['data']['tools_nccmilling_type'] = val
-
-            # Paint Tool
-            elif wdg_name == "gdb_p_overlap":
-                self.db_tool_dict[tool_id]['data']['tools_paintoverlap'] = val
-            elif wdg_name == "gdb_p_margin":
-                self.db_tool_dict[tool_id]['data']['tools_paintmargin'] = val
-            elif wdg_name == "gdb_p_method":
-                self.db_tool_dict[tool_id]['data']['tools_paintmethod'] = val
-            elif wdg_name == "gdb_p_connect":
-                self.db_tool_dict[tool_id]['data']['tools_pathconnect'] = val
-            elif wdg_name == "gdb_p_contour":
-                self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val
-
-        self.callback_app()
-
-    def on_tool_requested_from_app(self):
-        if not self.tree_widget.selectedItems():
-            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
-            return
-
-        for item in self.tree_widget.selectedItems():
-            tool_uid = item.data(0, QtCore.Qt.DisplayRole)
-
-            for key in self.db_tool_dict.keys():
-                if str(key) == str(tool_uid):
-                    selected_tool = self.db_tool_dict[key]
-                    self.on_tool_request(tool=selected_tool)
-
-    def on_cancel_tool(self):
-        for idx in range(self.app.ui.plot_tab_area.count()):
-            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
-                wdg = self.app.ui.plot_tab_area.widget(idx)
-                wdg.deleteLater()
-                self.app.ui.plot_tab_area.removeTab(idx)
-        self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
-
-    def resize_new_tool_table_widget(self, min_size, max_size):
-        """
-        Resize the table widget responsible for adding new tool in the Tool Database
-
-        :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
-        :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
-        :return:
-        """
-        t_height = self.t_height
-        if max_size > min_size:
-            t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
-
-        self.new_tool_table_widget.setMaximumHeight(t_height)
-
-    def closeEvent(self, QCloseEvent):
-        super().closeEvent(QCloseEvent)
-
-
 def color_variant(hex_color, bright_factor=1):
 def color_variant(hex_color, bright_factor=1):
     """
     """
     Takes a color in HEX format #FF00FF and produces a lighter or darker variant
     Takes a color in HEX format #FF00FF and produces a lighter or darker variant

+ 2399 - 0
FlatCAMDB.py

@@ -0,0 +1,2399 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
+    FCTree, RadioSet, FCFileSaveDialog
+from camlib import to_dict
+
+import sys
+import json
+
+from copy import deepcopy
+from datetime import datetime
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class ToolsDB(QtWidgets.QWidget):
+
+    mark_tools_rows = QtCore.pyqtSignal()
+
+    def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
+        super(ToolsDB, self).__init__(parent)
+
+        self.app = app
+        self.decimals = 4
+        self.callback_app = callback_on_edited
+
+        self.on_tool_request = callback_on_tool_request
+
+        self.offset_item_options = ["Path", "In", "Out", "Custom"]
+        self.type_item_options = ["Iso", "Rough", "Finish"]
+        self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
+
+        '''
+        dict to hold all the tools in the Tools DB
+        format:
+        {
+            tool_id: {
+                'name': 'new_tool'
+                'tooldia': self.app.defaults["geometry_cnctooldia"]
+                'offset': 'Path'
+                'offset_value': 0.0
+                'type':  _('Rough'),
+                'tool_type': 'C1'
+                'data': dict()
+            }
+        }
+        '''
+        self.db_tool_dict = {}
+
+        # layouts
+        layout = QtWidgets.QVBoxLayout()
+        self.setLayout(layout)
+
+        table_hlay = QtWidgets.QHBoxLayout()
+        layout.addLayout(table_hlay)
+
+        self.table_widget = FCTable(drag_drop=True)
+        self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+        table_hlay.addWidget(self.table_widget)
+
+        # set the number of columns and the headers tool tips
+        self.configure_table()
+
+        # pal = QtGui.QPalette()
+        # pal.setColor(QtGui.QPalette.Background, Qt.white)
+
+        # New Bookmark
+        new_vlay = QtWidgets.QVBoxLayout()
+        layout.addLayout(new_vlay)
+
+        # new_tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Tool"))
+        # new_vlay.addWidget(new_tool_lbl, alignment=QtCore.Qt.AlignBottom)
+
+        self.buttons_frame = QtWidgets.QFrame()
+        self.buttons_frame.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(self.buttons_frame)
+        self.buttons_box = QtWidgets.QHBoxLayout()
+        self.buttons_box.setContentsMargins(0, 0, 0, 0)
+        self.buttons_frame.setLayout(self.buttons_box)
+        self.buttons_frame.show()
+
+        add_entry_btn = FCButton(_("Add Geometry Tool in DB"))
+        add_entry_btn.setToolTip(
+            _("Add a new tool in the Tools Database.\n"
+              "It will be used in the Geometry UI.\n"
+              "You can edit it after it is added.")
+        )
+        self.buttons_box.addWidget(add_entry_btn)
+
+        # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
+        # add_fct_entry_btn.setToolTip(
+        #     _("Add a new tool in the Tools Database.\n"
+        #       "It will be used in the Paint/NCC Tools UI.\n"
+        #       "You can edit it after it is added.")
+        # )
+        # self.buttons_box.addWidget(add_fct_entry_btn)
+
+        remove_entry_btn = FCButton(_("Delete Tool from DB"))
+        remove_entry_btn.setToolTip(
+            _("Remove a selection of tools in the Tools Database.")
+        )
+        self.buttons_box.addWidget(remove_entry_btn)
+
+        export_db_btn = FCButton(_("Export DB"))
+        export_db_btn.setToolTip(
+            _("Save the Tools Database to a custom text file.")
+        )
+        self.buttons_box.addWidget(export_db_btn)
+
+        import_db_btn = FCButton(_("Import DB"))
+        import_db_btn.setToolTip(
+            _("Load the Tools Database information's from a custom text file.")
+        )
+        self.buttons_box.addWidget(import_db_btn)
+
+        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
+        self.add_tool_from_db.setToolTip(
+            _("Add a new tool in the Tools Table of the\n"
+              "active Geometry object after selecting a tool\n"
+              "in the Tools Database.")
+        )
+        self.add_tool_from_db.hide()
+
+        self.cancel_tool_from_db = FCButton(_("Cancel"))
+        self.cancel_tool_from_db.hide()
+
+        hlay = QtWidgets.QHBoxLayout()
+        layout.addLayout(hlay)
+        hlay.addWidget(self.add_tool_from_db)
+        hlay.addWidget(self.cancel_tool_from_db)
+        hlay.addStretch()
+
+        # ##############################################################################
+        # ######################## SIGNALS #############################################
+        # ##############################################################################
+
+        add_entry_btn.clicked.connect(self.on_tool_add)
+        remove_entry_btn.clicked.connect(self.on_tool_delete)
+        export_db_btn.clicked.connect(self.on_export_tools_db_file)
+        import_db_btn.clicked.connect(self.on_import_tools_db_file)
+        # closebtn.clicked.connect(self.accept)
+
+        self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
+        self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
+
+        self.setup_db_ui()
+
+    def configure_table(self):
+        self.table_widget.setColumnCount(27)
+        # self.table_widget.setColumnWidth(0, 20)
+        self.table_widget.setHorizontalHeaderLabels(
+            [
+                '#',
+                _("Tool Name"),
+                _("Tool Dia"),
+                _("Tool Offset"),
+                _("Custom Offset"),
+                _("Tool Type"),
+                _("Tool Shape"),
+                _("Cut Z"),
+                _("MultiDepth"),
+                _("DPP"),
+                _("V-Dia"),
+                _("V-Angle"),
+                _("Travel Z"),
+                _("FR"),
+                _("FR Z"),
+                _("FR Rapids"),
+                _("Spindle Speed"),
+                _("Dwell"),
+                _("Dwelltime"),
+                _("Preprocessor"),
+                _("ExtraCut"),
+                _("E-Cut Length"),
+                _("Toolchange"),
+                _("Toolchange XY"),
+                _("Toolchange Z"),
+                _("Start Z"),
+                _("End Z"),
+            ]
+        )
+        self.table_widget.horizontalHeaderItem(0).setToolTip(
+            _("Tool Index."))
+        self.table_widget.horizontalHeaderItem(1).setToolTip(
+            _("Tool name.\n"
+              "This is not used in the app, it's function\n"
+              "is to serve as a note for the user."))
+        self.table_widget.horizontalHeaderItem(2).setToolTip(
+            _("Tool Diameter."))
+        self.table_widget.horizontalHeaderItem(3).setToolTip(
+            _("Tool Offset.\n"
+              "Can be of a few types:\n"
+              "Path = zero offset\n"
+              "In = offset inside by half of tool diameter\n"
+              "Out = offset outside by half of tool diameter\n"
+              "Custom = custom offset using the Custom Offset value"))
+        self.table_widget.horizontalHeaderItem(4).setToolTip(
+            _("Custom Offset.\n"
+              "A value to be used as offset from the current path."))
+        self.table_widget.horizontalHeaderItem(5).setToolTip(
+            _("Tool Type.\n"
+              "Can be:\n"
+              "Iso = isolation cut\n"
+              "Rough = rough cut, low feedrate, multiple passes\n"
+              "Finish = finishing cut, high feedrate"))
+        self.table_widget.horizontalHeaderItem(6).setToolTip(
+            _("Tool Shape. \n"
+              "Can be:\n"
+              "C1 ... C4 = circular tool with x flutes\n"
+              "B = ball tip milling tool\n"
+              "V = v-shape milling tool"))
+        self.table_widget.horizontalHeaderItem(7).setToolTip(
+            _("Cutting Depth.\n"
+              "The depth at which to cut into material."))
+        self.table_widget.horizontalHeaderItem(8).setToolTip(
+            _("Multi Depth.\n"
+              "Selecting this will allow cutting in multiple passes,\n"
+              "each pass adding a DPP parameter depth."))
+        self.table_widget.horizontalHeaderItem(9).setToolTip(
+            _("DPP. Depth per Pass.\n"
+              "The value used to cut into material on each pass."))
+        self.table_widget.horizontalHeaderItem(10).setToolTip(
+            _("V-Dia.\n"
+              "Diameter of the tip for V-Shape Tools."))
+        self.table_widget.horizontalHeaderItem(11).setToolTip(
+            _("V-Agle.\n"
+              "Angle at the tip for the V-Shape Tools."))
+        self.table_widget.horizontalHeaderItem(12).setToolTip(
+            _("Clearance Height.\n"
+              "Height at which the milling bit will travel between cuts,\n"
+              "above the surface of the material, avoiding all fixtures."))
+        self.table_widget.horizontalHeaderItem(13).setToolTip(
+            _("FR. Feedrate\n"
+              "The speed on XY plane used while cutting into material."))
+        self.table_widget.horizontalHeaderItem(14).setToolTip(
+            _("FR Z. Feedrate Z\n"
+              "The speed on Z plane."))
+        self.table_widget.horizontalHeaderItem(15).setToolTip(
+            _("FR Rapids. Feedrate Rapids\n"
+              "Speed used while moving as fast as possible.\n"
+              "This is used only by some devices that can't use\n"
+              "the G0 g-code command. Mostly 3D printers."))
+        self.table_widget.horizontalHeaderItem(16).setToolTip(
+            _("Spindle Speed.\n"
+              "If it's left empty it will not be used.\n"
+              "The speed of the spindle in RPM."))
+        self.table_widget.horizontalHeaderItem(17).setToolTip(
+            _("Dwell.\n"
+              "Check this if a delay is needed to allow\n"
+              "the spindle motor to reach it's set speed."))
+        self.table_widget.horizontalHeaderItem(18).setToolTip(
+            _("Dwell Time.\n"
+              "A delay used to allow the motor spindle reach it's set speed."))
+        self.table_widget.horizontalHeaderItem(19).setToolTip(
+            _("Preprocessor.\n"
+              "A selection of files that will alter the generated G-code\n"
+              "to fit for a number of use cases."))
+        self.table_widget.horizontalHeaderItem(20).setToolTip(
+            _("Extra Cut.\n"
+              "If checked, after a isolation is finished an extra cut\n"
+              "will be added where the start and end of isolation meet\n"
+              "such as that this point is covered by this extra cut to\n"
+              "ensure a complete isolation."))
+        self.table_widget.horizontalHeaderItem(21).setToolTip(
+            _("Extra Cut length.\n"
+              "If checked, after a isolation is finished an extra cut\n"
+              "will be added where the start and end of isolation meet\n"
+              "such as that this point is covered by this extra cut to\n"
+              "ensure a complete isolation. This is the length of\n"
+              "the extra cut."))
+        self.table_widget.horizontalHeaderItem(22).setToolTip(
+            _("Toolchange.\n"
+              "It will create a toolchange event.\n"
+              "The kind of toolchange is determined by\n"
+              "the preprocessor file."))
+        self.table_widget.horizontalHeaderItem(23).setToolTip(
+            _("Toolchange XY.\n"
+              "A set of coordinates in the format (x, y).\n"
+              "Will determine the cartesian position of the point\n"
+              "where the tool change event take place."))
+        self.table_widget.horizontalHeaderItem(24).setToolTip(
+            _("Toolchange Z.\n"
+              "The position on Z plane where the tool change event take place."))
+        self.table_widget.horizontalHeaderItem(25).setToolTip(
+            _("Start Z.\n"
+              "If it's left empty it will not be used.\n"
+              "A position on Z plane to move immediately after job start."))
+        self.table_widget.horizontalHeaderItem(26).setToolTip(
+            _("End Z.\n"
+              "A position on Z plane to move immediately after job stop."))
+
+    def setup_db_ui(self):
+        filename = self.app.data_path + '/geo_tools_db.FlatDB'
+
+        # load the database tools from the file
+        try:
+            with open(filename) as f:
+                tools = f.read()
+        except IOError:
+            self.app.log.error("Could not load tools DB file.")
+            self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
+            return
+
+        try:
+            self.db_tool_dict = json.loads(tools)
+        except Exception:
+            e = sys.exc_info()[0]
+            self.app.log.error(str(e))
+            self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
+            return
+
+        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+
+        self.build_db_ui()
+
+        self.table_widget.setupContextMenu()
+        self.table_widget.addContextMenu(
+            _("Add to DB"), self.on_tool_add, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png"))
+        self.table_widget.addContextMenu(
+            _("Copy from DB"), self.on_tool_copy, icon=QtGui.QIcon(self.app.resource_location + "/copy16.png"))
+        self.table_widget.addContextMenu(
+            _("Delete from DB"), self.on_tool_delete, icon=QtGui.QIcon(self.app.resource_location + "/delete32.png"))
+
+    def build_db_ui(self):
+        self.ui_disconnect()
+        self.table_widget.setRowCount(len(self.db_tool_dict))
+
+        nr_crt = 0
+
+        for toolid, dict_val in self.db_tool_dict.items():
+            row = nr_crt
+            nr_crt += 1
+
+            t_name = dict_val['name']
+            try:
+                self.add_tool_table_line(row, name=t_name, widget=self.table_widget, tooldict=dict_val)
+            except Exception as e:
+                self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
+            vertical_header = self.table_widget.verticalHeader()
+            vertical_header.hide()
+
+            horizontal_header = self.table_widget.horizontalHeader()
+            horizontal_header.setMinimumSectionSize(10)
+            horizontal_header.setDefaultSectionSize(70)
+
+            self.table_widget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
+            for x in range(27):
+                self.table_widget.resizeColumnToContents(x)
+
+            horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+            # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+            # horizontal_header.setSectionResizeMode(13, QtWidgets.QHeaderView.Fixed)
+
+            horizontal_header.resizeSection(0, 20)
+            # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
+            # horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
+
+        self.ui_connect()
+
+    def add_tool_table_line(self, row, name, widget, tooldict):
+        data = tooldict['data']
+
+        nr_crt = row + 1
+        id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt))
+        # id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+        flags = id_item.flags() & ~QtCore.Qt.ItemIsEditable
+        id_item.setFlags(flags)
+        widget.setItem(row, 0, id_item)  # Tool name/id
+
+        tool_name_item = QtWidgets.QTableWidgetItem(name)
+        widget.setItem(row, 1, tool_name_item)
+
+        dia_item = FCDoubleSpinner()
+        dia_item.set_precision(self.decimals)
+        dia_item.setSingleStep(0.1)
+        dia_item.set_range(0.0, 9999.9999)
+        dia_item.set_value(float(tooldict['tooldia']))
+        widget.setCellWidget(row, 2, dia_item)
+
+        tool_offset_item = FCComboBox()
+        for item in self.offset_item_options:
+            tool_offset_item.addItem(item)
+        tool_offset_item.set_value(tooldict['offset'])
+        widget.setCellWidget(row, 3, tool_offset_item)
+
+        c_offset_item = FCDoubleSpinner()
+        c_offset_item.set_precision(self.decimals)
+        c_offset_item.setSingleStep(0.1)
+        c_offset_item.set_range(-9999.9999, 9999.9999)
+        c_offset_item.set_value(float(tooldict['offset_value']))
+        widget.setCellWidget(row, 4, c_offset_item)
+
+        tt_item = FCComboBox()
+        for item in self.type_item_options:
+            tt_item.addItem(item)
+        tt_item.set_value(tooldict['type'])
+        widget.setCellWidget(row, 5, tt_item)
+
+        tshape_item = FCComboBox()
+        for item in self.tool_type_item_options:
+            tshape_item.addItem(item)
+        tshape_item.set_value(tooldict['tool_type'])
+        widget.setCellWidget(row, 6, tshape_item)
+
+        cutz_item = FCDoubleSpinner()
+        cutz_item.set_precision(self.decimals)
+        cutz_item.setSingleStep(0.1)
+        if self.app.defaults['global_machinist_setting']:
+            cutz_item.set_range(-9999.9999, 9999.9999)
+        else:
+            cutz_item.set_range(-9999.9999, -0.0000)
+
+        cutz_item.set_value(float(data['cutz']))
+        widget.setCellWidget(row, 7, cutz_item)
+
+        multidepth_item = FCCheckBox()
+        multidepth_item.set_value(data['multidepth'])
+        widget.setCellWidget(row, 8, multidepth_item)
+
+        # to make the checkbox centered but it can no longer have it's value accessed - needs a fix using findchild()
+        # multidepth_item = QtWidgets.QWidget()
+        # cb = FCCheckBox()
+        # cb.set_value(data['multidepth'])
+        # qhboxlayout = QtWidgets.QHBoxLayout(multidepth_item)
+        # qhboxlayout.addWidget(cb)
+        # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
+        # qhboxlayout.setContentsMargins(0, 0, 0, 0)
+        # widget.setCellWidget(row, 8, multidepth_item)
+
+        depth_per_pass_item = FCDoubleSpinner()
+        depth_per_pass_item.set_precision(self.decimals)
+        depth_per_pass_item.setSingleStep(0.1)
+        depth_per_pass_item.set_range(0.0, 9999.9999)
+        depth_per_pass_item.set_value(float(data['depthperpass']))
+        widget.setCellWidget(row, 9, depth_per_pass_item)
+
+        vtip_dia_item = FCDoubleSpinner()
+        vtip_dia_item.set_precision(self.decimals)
+        vtip_dia_item.setSingleStep(0.1)
+        vtip_dia_item.set_range(0.0, 9999.9999)
+        vtip_dia_item.set_value(float(data['vtipdia']))
+        widget.setCellWidget(row, 10, vtip_dia_item)
+
+        vtip_angle_item = FCDoubleSpinner()
+        vtip_angle_item.set_precision(self.decimals)
+        vtip_angle_item.setSingleStep(0.1)
+        vtip_angle_item.set_range(-360.0, 360.0)
+        vtip_angle_item.set_value(float(data['vtipangle']))
+        widget.setCellWidget(row, 11, vtip_angle_item)
+
+        travelz_item = FCDoubleSpinner()
+        travelz_item.set_precision(self.decimals)
+        travelz_item.setSingleStep(0.1)
+        if self.app.defaults['global_machinist_setting']:
+            travelz_item.set_range(-9999.9999, 9999.9999)
+        else:
+            travelz_item.set_range(0.0000, 9999.9999)
+
+        travelz_item.set_value(float(data['travelz']))
+        widget.setCellWidget(row, 12, travelz_item)
+
+        fr_item = FCDoubleSpinner()
+        fr_item.set_precision(self.decimals)
+        fr_item.set_range(0.0, 9999.9999)
+        fr_item.set_value(float(data['feedrate']))
+        widget.setCellWidget(row, 13, fr_item)
+
+        frz_item = FCDoubleSpinner()
+        frz_item.set_precision(self.decimals)
+        frz_item.set_range(0.0, 9999.9999)
+        frz_item.set_value(float(data['feedrate_z']))
+        widget.setCellWidget(row, 14, frz_item)
+
+        frrapids_item = FCDoubleSpinner()
+        frrapids_item.set_precision(self.decimals)
+        frrapids_item.set_range(0.0, 9999.9999)
+        frrapids_item.set_value(float(data['feedrate_rapid']))
+        widget.setCellWidget(row, 15, frrapids_item)
+
+        spindlespeed_item = FCSpinner()
+        spindlespeed_item.set_range(0, 1000000)
+        spindlespeed_item.set_value(int(data['spindlespeed']))
+        spindlespeed_item.set_step(100)
+        widget.setCellWidget(row, 16, spindlespeed_item)
+
+        dwell_item = FCCheckBox()
+        dwell_item.set_value(data['dwell'])
+        widget.setCellWidget(row, 17, dwell_item)
+
+        dwelltime_item = FCDoubleSpinner()
+        dwelltime_item.set_precision(self.decimals)
+        dwelltime_item.set_range(0.0000, 9999.9999)
+        dwelltime_item.set_value(float(data['dwelltime']))
+        widget.setCellWidget(row, 18, dwelltime_item)
+
+        pp_item = FCComboBox()
+        for item in self.app.preprocessors:
+            pp_item.addItem(item)
+        pp_item.set_value(data['ppname_g'])
+        widget.setCellWidget(row, 19, pp_item)
+
+        ecut_item = FCCheckBox()
+        ecut_item.set_value(data['extracut'])
+        widget.setCellWidget(row, 20, ecut_item)
+
+        ecut_length_item = FCDoubleSpinner()
+        ecut_length_item.set_precision(self.decimals)
+        ecut_length_item.set_range(0.0000, 9999.9999)
+        ecut_length_item.set_value(data['extracut_length'])
+        widget.setCellWidget(row, 21, ecut_length_item)
+
+        toolchange_item = FCCheckBox()
+        toolchange_item.set_value(data['toolchange'])
+        widget.setCellWidget(row, 22, toolchange_item)
+
+        toolchangexy_item = QtWidgets.QTableWidgetItem(str(data['toolchangexy']) if data['toolchangexy'] else '')
+        widget.setItem(row, 23, toolchangexy_item)
+
+        toolchangez_item = FCDoubleSpinner()
+        toolchangez_item.set_precision(self.decimals)
+        toolchangez_item.setSingleStep(0.1)
+        if self.app.defaults['global_machinist_setting']:
+            toolchangez_item.set_range(-9999.9999, 9999.9999)
+        else:
+            toolchangez_item.set_range(0.0000, 9999.9999)
+
+        toolchangez_item.set_value(float(data['toolchangez']))
+        widget.setCellWidget(row, 24, toolchangez_item)
+
+        startz_item = QtWidgets.QTableWidgetItem(str(data['startz']) if data['startz'] else '')
+        widget.setItem(row, 25, startz_item)
+
+        endz_item = FCDoubleSpinner()
+        endz_item.set_precision(self.decimals)
+        endz_item.setSingleStep(0.1)
+        if self.app.defaults['global_machinist_setting']:
+            endz_item.set_range(-9999.9999, 9999.9999)
+        else:
+            endz_item.set_range(0.0000, 9999.9999)
+
+        endz_item.set_value(float(data['endz']))
+        widget.setCellWidget(row, 26, endz_item)
+
+    def on_tool_add(self):
+        """
+        Add a tool in the DB Tool Table
+        :return: None
+        """
+
+        default_data = {}
+        default_data.update({
+            "cutz": float(self.app.defaults["geometry_cutz"]),
+            "multidepth": self.app.defaults["geometry_multidepth"],
+            "depthperpass": float(self.app.defaults["geometry_depthperpass"]),
+            "vtipdia": float(self.app.defaults["geometry_vtipdia"]),
+            "vtipangle": float(self.app.defaults["geometry_vtipangle"]),
+            "travelz": float(self.app.defaults["geometry_travelz"]),
+            "feedrate": float(self.app.defaults["geometry_feedrate"]),
+            "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]),
+            "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]),
+            "spindlespeed": self.app.defaults["geometry_spindlespeed"],
+            "dwell": self.app.defaults["geometry_dwell"],
+            "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
+            "ppname_g": self.app.defaults["geometry_ppname_g"],
+            "extracut": self.app.defaults["geometry_extracut"],
+            "extracut_length": float(self.app.defaults["geometry_extracut_length"]),
+            "toolchange": self.app.defaults["geometry_toolchange"],
+            "toolchangexy": self.app.defaults["geometry_toolchangexy"],
+            "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
+            "startz": self.app.defaults["geometry_startz"],
+            "endz": float(self.app.defaults["geometry_endz"])
+        })
+
+        dict_elem = {}
+        dict_elem['name'] = 'new_tool'
+        if type(self.app.defaults["geometry_cnctooldia"]) == float:
+            dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
+        else:
+            try:
+                tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
+                tools_diameters = [eval(a) for a in tools_string if a != '']
+                dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
+            except Exception as e:
+                self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
+                return
+
+        dict_elem['offset'] = 'Path'
+        dict_elem['offset_value'] = 0.0
+        dict_elem['type'] = 'Rough'
+        dict_elem['tool_type'] = 'C1'
+        dict_elem['data'] = default_data
+
+        new_toolid = len(self.db_tool_dict) + 1
+        self.db_tool_dict[new_toolid] = deepcopy(dict_elem)
+
+        # add the new entry to the Tools DB table
+        self.build_db_ui()
+        self.callback_on_edited()
+        self.app.inform.emit('[success] %s' % _("Tool added to DB."))
+
+    def on_tool_copy(self):
+        """
+        Copy a selection of Tools in the Tools DB table
+        :return:
+        """
+        new_tool_id = self.table_widget.rowCount() + 1
+        for model_index in self.table_widget.selectionModel().selectedRows():
+            # index = QtCore.QPersistentModelIndex(model_index)
+            old_tool_id = self.table_widget.item(model_index.row(), 0).text()
+            new_tool_id += 1
+
+            for toolid, dict_val in list(self.db_tool_dict.items()):
+                if int(old_tool_id) == int(toolid):
+                    self.db_tool_dict.update({
+                        new_tool_id: deepcopy(dict_val)
+                    })
+
+        self.build_db_ui()
+        self.callback_on_edited()
+        self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
+
+    def on_tool_delete(self):
+        """
+        Delete a selection of Tools in the Tools DB table
+        :return:
+        """
+        for model_index in self.table_widget.selectionModel().selectedRows():
+            # index = QtCore.QPersistentModelIndex(model_index)
+            toolname_to_remove = self.table_widget.item(model_index.row(), 0).text()
+
+            for toolid, dict_val in list(self.db_tool_dict.items()):
+                if int(toolname_to_remove) == int(toolid):
+                    # remove from the storage
+                    self.db_tool_dict.pop(toolid, None)
+
+        self.build_db_ui()
+        self.callback_on_edited()
+        self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
+
+    def on_export_tools_db_file(self):
+        self.app.report_usage("on_export_tools_db_file")
+        self.app.log.debug("on_export_tools_db_file()")
+
+        date = str(datetime.today()).rpartition('.')[0]
+        date = ''.join(c for c in date if c not in ':-')
+        date = date.replace(' ', '_')
+
+        filter__ = "Text File (*.TXT);;All Files (*.*)"
+        filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Tools Database"),
+                                                           directory='{l_save}/FlatCAM_{n}_{date}'.format(
+                                                               l_save=str(self.app.get_last_save_folder()),
+                                                               n=_("Tools_Database"),
+                                                               date=date),
+                                                           filter=filter__)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+            return
+        else:
+            try:
+                f = open(filename, 'w')
+                f.close()
+            except PermissionError:
+                self.app.inform.emit('[WARNING] %s' %
+                                     _("Permission denied, saving not possible.\n"
+                                       "Most likely another app is holding the file open and not accessible."))
+                return
+            except IOError:
+                self.app.log.debug('Creating a new Tools DB file ...')
+                f = open(filename, 'w')
+                f.close()
+            except Exception:
+                e = sys.exc_info()[0]
+                self.app.log.error("Could not load Tools DB file.")
+                self.app.log.error(str(e))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
+                return
+
+            # Save update options
+            try:
+                # Save Tools DB in a file
+                try:
+                    with open(filename, "w") as f:
+                        json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
+                except Exception as e:
+                    self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
+                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                    return
+            except Exception:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                return
+
+        self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
+
+    def on_import_tools_db_file(self):
+        self.app.report_usage("on_import_tools_db_file")
+        self.app.log.debug("on_import_tools_db_file()")
+
+        filter__ = "Text File (*.TXT);;All Files (*.*)"
+        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+        else:
+            try:
+                with open(filename) as f:
+                    tools_in_db = f.read()
+            except IOError:
+                self.app.log.error("Could not load Tools DB file.")
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
+                return
+
+            try:
+                self.db_tool_dict = json.loads(tools_in_db)
+            except Exception:
+                e = sys.exc_info()[0]
+                self.app.log.error(str(e))
+                self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
+                return
+
+            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+            self.build_db_ui()
+            self.callback_on_edited()
+
+    def on_save_tools_db(self, silent=False):
+        self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.")
+
+        filename = self.app.data_path + "/geo_tools_db.FlatDB"
+
+        # Preferences save, update the color of the Tools DB Tab text
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
+
+                # Save Tools DB in a file
+                try:
+                    f = open(filename, "w")
+                    json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
+                    f.close()
+                except Exception as e:
+                    self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
+                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                    return
+
+                if not silent:
+                    self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
+
+    def ui_connect(self):
+        try:
+            try:
+                self.table_widget.itemChanged.disconnect(self.callback_on_edited)
+            except (TypeError, AttributeError):
+                pass
+            self.table_widget.itemChanged.connect(self.callback_on_edited)
+        except AttributeError:
+            pass
+
+        for row in range(self.table_widget.rowCount()):
+            for col in range(self.table_widget.columnCount()):
+                # ComboBox
+                try:
+                    try:
+                        self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
+                    except (TypeError, AttributeError):
+                        pass
+                    self.table_widget.cellWidget(row, col).currentIndexChanged.connect(self.callback_on_edited)
+                except AttributeError:
+                    pass
+
+                # CheckBox
+                try:
+                    try:
+                        self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
+                    except (TypeError, AttributeError):
+                        pass
+                    self.table_widget.cellWidget(row, col).toggled.connect(self.callback_on_edited)
+                except AttributeError:
+                    pass
+
+                # SpinBox, DoubleSpinBox
+                try:
+                    try:
+                        self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
+                    except (TypeError, AttributeError):
+                        pass
+                    self.table_widget.cellWidget(row, col).valueChanged.connect(self.callback_on_edited)
+                except AttributeError:
+                    pass
+
+    def ui_disconnect(self):
+        try:
+            self.table_widget.itemChanged.disconnect(self.callback_on_edited)
+        except (TypeError, AttributeError):
+            pass
+
+        for row in range(self.table_widget.rowCount()):
+            for col in range(self.table_widget.columnCount()):
+                # ComboBox
+                try:
+                    self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited)
+                except (TypeError, AttributeError):
+                    pass
+
+                # CheckBox
+                try:
+                    self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited)
+                except (TypeError, AttributeError):
+                    pass
+
+                # SpinBox, DoubleSpinBox
+                try:
+                    self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited)
+                except (TypeError, AttributeError):
+                    pass
+
+    def callback_on_edited(self):
+
+        # update the dictionary storage self.db_tool_dict
+        self.db_tool_dict.clear()
+        dict_elem = {}
+        default_data = {}
+
+        for row in range(self.table_widget.rowCount()):
+            new_toolid = row + 1
+            for col in range(self.table_widget.columnCount()):
+                column_header_text = self.table_widget.horizontalHeaderItem(col).text()
+                if column_header_text == _('Tool Name'):
+                    dict_elem['name'] = self.table_widget.item(row, col).text()
+                elif column_header_text == _('Tool Dia'):
+                    dict_elem['tooldia'] = self.table_widget.cellWidget(row, col).get_value()
+                elif column_header_text == _('Tool Offset'):
+                    dict_elem['offset'] = self.table_widget.cellWidget(row, col).get_value()
+                elif column_header_text == _('Custom Offset'):
+                    dict_elem['offset_value'] = self.table_widget.cellWidget(row, col).get_value()
+                elif column_header_text == _('Tool Type'):
+                    dict_elem['type'] = self.table_widget.cellWidget(row, col).get_value()
+                elif column_header_text == _('Tool Shape'):
+                    dict_elem['tool_type'] = self.table_widget.cellWidget(row, col).get_value()
+                else:
+                    if column_header_text == _('Cut Z'):
+                        default_data['cutz'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('MultiDepth'):
+                        default_data['multidepth'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('DPP'):
+                        default_data['depthperpass'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('V-Dia'):
+                        default_data['vtipdia'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('V-Angle'):
+                        default_data['vtipangle'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Travel Z'):
+                        default_data['travelz'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('FR'):
+                        default_data['feedrate'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('FR Z'):
+                        default_data['feedrate_z'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('FR Rapids'):
+                        default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Spindle Speed'):
+                        default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Dwell'):
+                        default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Dwelltime'):
+                        default_data['dwelltime'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Preprocessor'):
+                        default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('ExtraCut'):
+                        default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _("E-Cut Length"):
+                        default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Toolchange'):
+                        default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Toolchange XY'):
+                        default_data['toolchangexy'] = self.table_widget.item(row, col).text()
+                    elif column_header_text == _('Toolchange Z'):
+                        default_data['toolchangez'] = self.table_widget.cellWidget(row, col).get_value()
+                    elif column_header_text == _('Start Z'):
+                        default_data['startz'] = float(self.table_widget.item(row, col).text()) \
+                            if self.table_widget.item(row, col).text() != '' else None
+                    elif column_header_text == _('End Z'):
+                        default_data['endz'] = self.table_widget.cellWidget(row, col).get_value()
+
+            dict_elem['data'] = default_data
+            self.db_tool_dict.update(
+                {
+                    new_toolid: deepcopy(dict_elem)
+                }
+            )
+
+        self.callback_app()
+
+    def on_tool_requested_from_app(self):
+        if not self.table_widget.selectionModel().selectedRows():
+            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
+            return
+
+        model_index_list = self.table_widget.selectionModel().selectedRows()
+        for model_index in model_index_list:
+            selected_row = model_index.row()
+            tool_uid = selected_row + 1
+            for key in self.db_tool_dict.keys():
+                if str(key) == str(tool_uid):
+                    selected_tool = self.db_tool_dict[key]
+                    self.on_tool_request(tool=selected_tool)
+
+    def on_cancel_tool(self):
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                wdg = self.app.ui.plot_tab_area.widget(idx)
+                wdg.deleteLater()
+                self.app.ui.plot_tab_area.removeTab(idx)
+        self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
+
+    def resize_new_tool_table_widget(self, min_size, max_size):
+        """
+        Resize the table widget responsible for adding new tool in the Tool Database
+
+        :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
+        :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
+        :return:
+        """
+        t_height = self.t_height
+        if max_size > min_size:
+            t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
+
+        self.new_tool_table_widget.setMaximumHeight(t_height)
+
+    def closeEvent(self, QCloseEvent):
+        super().closeEvent(QCloseEvent)
+
+
+class ToolsDB2(QtWidgets.QWidget):
+
+    mark_tools_rows = QtCore.pyqtSignal()
+
+    def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
+        super(ToolsDB2, self).__init__(parent)
+
+        self.app = app
+        self.decimals = self.app.decimals
+        self.callback_app = callback_on_edited
+
+        self.on_tool_request = callback_on_tool_request
+
+        self.offset_item_options = ["Path", "In", "Out", "Custom"]
+        self.type_item_options = ["Iso", "Rough", "Finish"]
+        self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
+
+        '''
+        dict to hold all the tools in the Tools DB
+        format:
+        {
+            tool_id: {
+                'name': 'new_tool'
+                'tooldia': self.app.defaults["geometry_cnctooldia"]
+                'offset': 'Path'
+                'offset_value': 0.0
+                'type':  _('Rough'),
+                'tool_type': 'C1'
+                'data': dict()
+            }
+        }
+        '''
+        self.db_tool_dict = {}
+
+        # layouts
+        grid_layout = QtWidgets.QGridLayout()
+        grid_layout.setColumnStretch(0, 0)
+        grid_layout.setColumnStretch(1, 1)
+
+        self.setLayout(grid_layout)
+
+        tree_layout = QtWidgets.QVBoxLayout()
+        grid_layout.addLayout(tree_layout, 0, 0)
+
+        self.tree_widget = FCTree(columns=2, header_hidden=False, protected_column=[0])
+        self.tree_widget.setHeaderLabels(["ID", "Tool Name"])
+        self.tree_widget.setIndentation(0)
+        self.tree_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+        self.tree_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+
+        # set alternating colors
+        # self.tree_widget.setAlternatingRowColors(True)
+        # p = QtGui.QPalette()
+        # p.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(226, 237, 253) )
+        # self.tree_widget.setPalette(p)
+
+        self.tree_widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
+        tree_layout.addWidget(self.tree_widget)
+
+        param_hlay = QtWidgets.QHBoxLayout()
+        param_area = QtWidgets.QScrollArea()
+        param_widget = QtWidgets.QWidget()
+        param_widget.setLayout(param_hlay)
+
+        param_area.setWidget(param_widget)
+        param_area.setWidgetResizable(True)
+
+        grid_layout.addWidget(param_area, 0, 1)
+
+        # ###########################################################################
+        # ############## The UI form ################################################
+        # ###########################################################################
+        self.basic_box = QtWidgets.QGroupBox()
+        self.basic_box.setStyleSheet("""
+        QGroupBox
+        {
+            font-size: 16px;
+            font-weight: bold;
+        }
+        """)
+        self.basic_vlay = QtWidgets.QVBoxLayout()
+        self.basic_box.setTitle(_("Basic Geo Parameters"))
+        self.basic_box.setFixedWidth(250)
+
+        self.advanced_box = QtWidgets.QGroupBox()
+        self.advanced_box.setStyleSheet("""
+                QGroupBox
+                {
+                    font-size: 16px;
+                    font-weight: bold;
+                }
+                """)
+        self.advanced_vlay = QtWidgets.QVBoxLayout()
+        self.advanced_box.setTitle(_("Advanced Geo Parameters"))
+        self.advanced_box.setFixedWidth(250)
+
+        self.ncc_box = QtWidgets.QGroupBox()
+        self.ncc_box.setStyleSheet("""
+                        QGroupBox
+                        {
+                            font-size: 16px;
+                            font-weight: bold;
+                        }
+                        """)
+        self.ncc_vlay = QtWidgets.QVBoxLayout()
+        self.ncc_box.setTitle(_("NCC Parameters"))
+        self.ncc_box.setFixedWidth(250)
+
+        self.paint_box = QtWidgets.QGroupBox()
+        self.paint_box.setStyleSheet("""
+                        QGroupBox
+                        {
+                            font-size: 16px;
+                            font-weight: bold;
+                        }
+                        """)
+        self.paint_vlay = QtWidgets.QVBoxLayout()
+        self.paint_box.setTitle(_("Paint Parameters"))
+        self.paint_box.setFixedWidth(250)
+
+        self.basic_box.setLayout(self.basic_vlay)
+        self.advanced_box.setLayout(self.advanced_vlay)
+        self.ncc_box.setLayout(self.ncc_vlay)
+        self.paint_box.setLayout(self.paint_vlay)
+
+        geo_vlay = QtWidgets.QVBoxLayout()
+        geo_vlay.addWidget(self.basic_box)
+        geo_vlay.addWidget(self.advanced_box)
+        geo_vlay.addStretch()
+
+        tools_vlay = QtWidgets.QVBoxLayout()
+        tools_vlay.addWidget(self.ncc_box)
+        tools_vlay.addWidget(self.paint_box)
+        tools_vlay.addStretch()
+
+        param_hlay.addLayout(geo_vlay)
+        param_hlay.addLayout(tools_vlay)
+        param_hlay.addStretch()
+
+        # ###########################################################################
+        # ############### BASIC UI form #############################################
+        # ###########################################################################
+
+        self.grid0 = QtWidgets.QGridLayout()
+        self.basic_vlay.addLayout(self.grid0)
+        self.grid0.setColumnStretch(0, 0)
+        self.grid0.setColumnStretch(1, 1)
+        self.basic_vlay.addStretch()
+
+        # Tool Name
+        self.name_label = QtWidgets.QLabel('<span style="color:red;"><b>%s:</b></span>' % _('Tool Name'))
+        self.name_label.setToolTip(
+            _("Tool name.\n"
+              "This is not used in the app, it's function\n"
+              "is to serve as a note for the user."))
+
+        self.name_entry = FCEntry()
+        self.name_entry.setObjectName('gdb_name')
+
+        self.grid0.addWidget(self.name_label, 0, 0)
+        self.grid0.addWidget(self.name_entry, 0, 1)
+
+        # Tool Dia
+        self.dia_label = QtWidgets.QLabel('%s:' % _('Tool Dia'))
+        self.dia_label.setToolTip(
+            _("Tool Diameter."))
+
+        self.dia_entry = FCDoubleSpinner()
+        self.dia_entry.set_range(-9999.9999, 9999.9999)
+        self.dia_entry.set_precision(self.decimals)
+        self.dia_entry.setObjectName('gdb_dia')
+
+        self.grid0.addWidget(self.dia_label, 1, 0)
+        self.grid0.addWidget(self.dia_entry, 1, 1)
+
+        # Tool Shape
+        self.shape_label = QtWidgets.QLabel('%s:' % _('Tool Shape'))
+        self.shape_label.setToolTip(
+            _("Tool Shape. \n"
+              "Can be:\n"
+              "C1 ... C4 = circular tool with x flutes\n"
+              "B = ball tip milling tool\n"
+              "V = v-shape milling tool"))
+
+        self.shape_combo = FCComboBox()
+        self.shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"])
+        self.shape_combo.setObjectName('gdb_shape')
+
+        self.grid0.addWidget(self.shape_label, 2, 0)
+        self.grid0.addWidget(self.shape_combo, 2, 1)
+
+        # Cut Z
+        self.cutz_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
+        self.cutz_label.setToolTip(
+            _("Cutting Depth.\n"
+              "The depth at which to cut into material."))
+
+        self.cutz_entry = FCDoubleSpinner()
+        self.cutz_entry.set_range(-9999.9999, 9999.9999)
+        self.cutz_entry.set_precision(self.decimals)
+        self.cutz_entry.setObjectName('gdb_cutz')
+
+        self.grid0.addWidget(self.cutz_label, 4, 0)
+        self.grid0.addWidget(self.cutz_entry, 4, 1)
+
+        # Multi Depth
+        self.multidepth_label = QtWidgets.QLabel('%s:' % _("MultiDepth"))
+        self.multidepth_label.setToolTip(
+            _("Multi Depth.\n"
+              "Selecting this will allow cutting in multiple passes,\n"
+              "each pass adding a DPP parameter depth."))
+
+        self.multidepth_cb = FCCheckBox()
+        self.multidepth_cb.setObjectName('gdb_multidepth')
+
+        self.grid0.addWidget(self.multidepth_label, 5, 0)
+        self.grid0.addWidget(self.multidepth_cb, 5, 1)
+
+        # Depth Per Pass
+        self.dpp_label = QtWidgets.QLabel('%s:' % _("DPP"))
+        self.dpp_label.setToolTip(
+            _("DPP. Depth per Pass.\n"
+              "The value used to cut into material on each pass."))
+
+        self.multidepth_entry = FCDoubleSpinner()
+        self.multidepth_entry.set_range(-9999.9999, 9999.9999)
+        self.multidepth_entry.set_precision(self.decimals)
+        self.multidepth_entry.setObjectName('gdb_multidepth_entry')
+
+        self.grid0.addWidget(self.dpp_label, 7, 0)
+        self.grid0.addWidget(self.multidepth_entry, 7, 1)
+
+        # Travel Z
+        self.travelz_label = QtWidgets.QLabel('%s:' % _("Travel Z"))
+        self.travelz_label.setToolTip(
+            _("Clearance Height.\n"
+              "Height at which the milling bit will travel between cuts,\n"
+              "above the surface of the material, avoiding all fixtures."))
+
+        self.travelz_entry = FCDoubleSpinner()
+        self.travelz_entry.set_range(-9999.9999, 9999.9999)
+        self.travelz_entry.set_precision(self.decimals)
+        self.travelz_entry.setObjectName('gdb_travel')
+
+        self.grid0.addWidget(self.travelz_label, 9, 0)
+        self.grid0.addWidget(self.travelz_entry, 9, 1)
+
+        # Feedrate X-Y
+        self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y"))
+        self.frxy_label.setToolTip(
+            _("Feedrate X-Y. Feedrate\n"
+              "The speed on XY plane used while cutting into material."))
+
+        self.frxy_entry = FCDoubleSpinner()
+        self.frxy_entry.set_range(-999999.9999, 999999.9999)
+        self.frxy_entry.set_precision(self.decimals)
+        self.frxy_entry.setObjectName('gdb_frxy')
+
+        self.grid0.addWidget(self.frxy_label, 12, 0)
+        self.grid0.addWidget(self.frxy_entry, 12, 1)
+
+        # Feedrate Z
+        self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z"))
+        self.frz_label.setToolTip(
+            _("Feedrate Z\n"
+              "The speed on Z plane."))
+
+        self.frz_entry = FCDoubleSpinner()
+        self.frz_entry.set_range(-999999.9999, 999999.9999)
+        self.frz_entry.set_precision(self.decimals)
+        self.frz_entry.setObjectName('gdb_frz')
+
+        self.grid0.addWidget(self.frz_label, 14, 0)
+        self.grid0.addWidget(self.frz_entry, 14, 1)
+
+        # Spindle Spped
+        self.spindle_label = QtWidgets.QLabel('%s:' % _("Spindle Speed"))
+        self.spindle_label.setToolTip(
+            _("Spindle Speed.\n"
+              "If it's left empty it will not be used.\n"
+              "The speed of the spindle in RPM."))
+
+        self.spindle_entry = FCDoubleSpinner()
+        self.spindle_entry.set_range(-999999.9999, 999999.9999)
+        self.spindle_entry.set_precision(self.decimals)
+        self.spindle_entry.setObjectName('gdb_spindle')
+
+        self.grid0.addWidget(self.spindle_label, 15, 0)
+        self.grid0.addWidget(self.spindle_entry, 15, 1)
+
+        # Dwell
+        self.dwell_label = QtWidgets.QLabel('%s:' % _("Dwell"))
+        self.dwell_label.setToolTip(
+            _("Dwell.\n"
+              "Check this if a delay is needed to allow\n"
+              "the spindle motor to reach it's set speed."))
+
+        self.dwell_cb = FCCheckBox()
+        self.dwell_cb.setObjectName('gdb_dwell')
+
+        self.grid0.addWidget(self.dwell_label, 16, 0)
+        self.grid0.addWidget(self.dwell_cb, 16, 1)
+
+        # Dwell Time
+        self.dwelltime_label = QtWidgets.QLabel('%s:' % _("Dwelltime"))
+        self.dwelltime_label.setToolTip(
+            _("Dwell Time.\n"
+              "A delay used to allow the motor spindle reach it's set speed."))
+
+        self.dwelltime_entry = FCDoubleSpinner()
+        self.dwelltime_entry.set_range(0.0000, 9999.9999)
+        self.dwelltime_entry.set_precision(self.decimals)
+        self.dwelltime_entry.setObjectName('gdb_dwelltime')
+
+        self.grid0.addWidget(self.dwelltime_label, 17, 0)
+        self.grid0.addWidget(self.dwelltime_entry, 17, 1)
+
+        # ###########################################################################
+        # ############### ADVANCED UI form ##########################################
+        # ###########################################################################
+
+        self.grid1 = QtWidgets.QGridLayout()
+        self.advanced_vlay.addLayout(self.grid1)
+        self.grid1.setColumnStretch(0, 0)
+        self.grid1.setColumnStretch(1, 1)
+        self.advanced_vlay.addStretch()
+
+        # Tool Type
+        self.type_label = QtWidgets.QLabel('%s:' % _("Tool Type"))
+        self.type_label.setToolTip(
+            _("Tool Type.\n"
+              "Can be:\n"
+              "Iso = isolation cut\n"
+              "Rough = rough cut, low feedrate, multiple passes\n"
+              "Finish = finishing cut, high feedrate"))
+
+        self.type_combo = FCComboBox()
+        self.type_combo.addItems(["Iso", "Rough", "Finish"])
+        self.type_combo.setObjectName('gdb_type')
+
+        self.grid1.addWidget(self.type_label, 0, 0)
+        self.grid1.addWidget(self.type_combo, 0, 1)
+
+        # Tool Offset
+        self.tooloffset_label = QtWidgets.QLabel('%s:' % _('Tool Offset'))
+        self.tooloffset_label.setToolTip(
+            _("Tool Offset.\n"
+              "Can be of a few types:\n"
+              "Path = zero offset\n"
+              "In = offset inside by half of tool diameter\n"
+              "Out = offset outside by half of tool diameter\n"
+              "Custom = custom offset using the Custom Offset value"))
+
+        self.tooloffset_combo = FCComboBox()
+        self.tooloffset_combo.addItems(["Path", "In", "Out", "Custom"])
+        self.tooloffset_combo.setObjectName('gdb_tool_offset')
+
+        self.grid1.addWidget(self.tooloffset_label, 2, 0)
+        self.grid1.addWidget(self.tooloffset_combo, 2, 1)
+
+        # Custom Offset
+        self.custom_offset_label = QtWidgets.QLabel('%s:' % _("Custom Offset"))
+        self.custom_offset_label.setToolTip(
+            _("Custom Offset.\n"
+              "A value to be used as offset from the current path."))
+
+        self.custom_offset_entry = FCDoubleSpinner()
+        self.custom_offset_entry.set_range(-9999.9999, 9999.9999)
+        self.custom_offset_entry.set_precision(self.decimals)
+        self.custom_offset_entry.setObjectName('gdb_custom_offset')
+
+        self.grid1.addWidget(self.custom_offset_label, 5, 0)
+        self.grid1.addWidget(self.custom_offset_entry, 5, 1)
+
+        # V-Dia
+        self.vdia_label = QtWidgets.QLabel('%s:' % _("V-Dia"))
+        self.vdia_label.setToolTip(
+            _("V-Dia.\n"
+              "Diameter of the tip for V-Shape Tools."))
+
+        self.vdia_entry = FCDoubleSpinner()
+        self.vdia_entry.set_range(0.0000, 9999.9999)
+        self.vdia_entry.set_precision(self.decimals)
+        self.vdia_entry.setObjectName('gdb_vdia')
+
+        self.grid1.addWidget(self.vdia_label, 7, 0)
+        self.grid1.addWidget(self.vdia_entry, 7, 1)
+
+        # V-Angle
+        self.vangle_label = QtWidgets.QLabel('%s:' % _("V-Angle"))
+        self.vangle_label.setToolTip(
+            _("V-Agle.\n"
+              "Angle at the tip for the V-Shape Tools."))
+
+        self.vangle_entry = FCDoubleSpinner()
+        self.vangle_entry.set_range(-360.0, 360.0)
+        self.vangle_entry.set_precision(self.decimals)
+        self.vangle_entry.setObjectName('gdb_vangle')
+
+        self.grid1.addWidget(self.vangle_label, 8, 0)
+        self.grid1.addWidget(self.vangle_entry, 8, 1)
+
+        # Feedrate Rapids
+        self.frapids_label = QtWidgets.QLabel('%s:' % _("FR Rapids"))
+        self.frapids_label.setToolTip(
+            _("FR Rapids. Feedrate Rapids\n"
+              "Speed used while moving as fast as possible.\n"
+              "This is used only by some devices that can't use\n"
+              "the G0 g-code command. Mostly 3D printers."))
+
+        self.frapids_entry = FCDoubleSpinner()
+        self.frapids_entry.set_range(0.0000, 9999.9999)
+        self.frapids_entry.set_precision(self.decimals)
+        self.frapids_entry.setObjectName('gdb_frapids')
+
+        self.grid1.addWidget(self.frapids_label, 10, 0)
+        self.grid1.addWidget(self.frapids_entry, 10, 1)
+
+        # Extra Cut
+        self.ecut_label = QtWidgets.QLabel('%s:' % _("ExtraCut"))
+        self.ecut_label.setToolTip(
+            _("Extra Cut.\n"
+              "If checked, after a isolation is finished an extra cut\n"
+              "will be added where the start and end of isolation meet\n"
+              "such as that this point is covered by this extra cut to\n"
+              "ensure a complete isolation."))
+
+        self.ecut_cb = FCCheckBox()
+        self.ecut_cb.setObjectName('gdb_ecut')
+
+        self.grid1.addWidget(self.ecut_label, 12, 0)
+        self.grid1.addWidget(self.ecut_cb, 12, 1)
+
+        # Extra Cut Length
+        self.ecut_length_label = QtWidgets.QLabel('%s:' % _("E-Cut Length"))
+        self.ecut_length_label.setToolTip(
+            _("Extra Cut length.\n"
+              "If checked, after a isolation is finished an extra cut\n"
+              "will be added where the start and end of isolation meet\n"
+              "such as that this point is covered by this extra cut to\n"
+              "ensure a complete isolation. This is the length of\n"
+              "the extra cut."))
+
+        self.ecut_length_entry = FCDoubleSpinner()
+        self.ecut_length_entry.set_range(0.0000, 9999.9999)
+        self.ecut_length_entry.set_precision(self.decimals)
+        self.ecut_length_entry.setObjectName('gdb_ecut_length')
+
+        self.grid1.addWidget(self.ecut_length_label, 13, 0)
+        self.grid1.addWidget(self.ecut_length_entry, 13, 1)
+
+        # ###########################################################################
+        # ############### NCC UI form ###############################################
+        # ###########################################################################
+
+        self.grid2 = QtWidgets.QGridLayout()
+        self.ncc_vlay.addLayout(self.grid2)
+        self.grid2.setColumnStretch(0, 0)
+        self.grid2.setColumnStretch(1, 1)
+        self.ncc_vlay.addStretch()
+
+        # Operation
+        op_label = QtWidgets.QLabel('%s:' % _('Operation'))
+        op_label.setToolTip(
+            _("The 'Operation' can be:\n"
+              "- Isolation -> will ensure that the non-copper clearing is always complete.\n"
+              "If it's not successful then the non-copper clearing will fail, too.\n"
+              "- Clear -> the regular non-copper clearing.")
+        )
+
+        self.op_radio = RadioSet([
+            {"label": _("Clear"), "value": "clear"},
+            {"label": _("Isolation"), "value": "iso"}
+        ], orientation='horizontal', stretch=False)
+        self.op_radio.setObjectName("gdb_n_operation")
+
+        self.grid2.addWidget(op_label, 13, 0)
+        self.grid2.addWidget(self.op_radio, 13, 1)
+
+        # Milling Type Radio Button
+        self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
+        self.milling_type_label.setToolTip(
+            _("Milling type when the selected tool is of type: 'iso_op':\n"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+
+        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
+                                            {'label': _('Conventional'), 'value': 'cv'}])
+        self.milling_type_radio.setToolTip(
+            _("Milling type when the selected tool is of type: 'iso_op':\n"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+        self.milling_type_radio.setObjectName("gdb_n_milling_type")
+
+        self.grid2.addWidget(self.milling_type_label, 14, 0)
+        self.grid2.addWidget(self.milling_type_radio, 14, 1)
+
+        # Overlap Entry
+        nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
+        nccoverlabel.setToolTip(
+            _("How much (percentage) of the tool width to overlap each tool pass.\n"
+              "Adjust the value starting with lower values\n"
+              "and increasing it if areas that should be cleared are still \n"
+              "not cleared.\n"
+              "Lower values = faster processing, faster execution on CNC.\n"
+              "Higher values = slow processing and slow execution on CNC\n"
+              "due of too many paths.")
+        )
+        self.ncc_overlap_entry = FCDoubleSpinner(suffix='%')
+        self.ncc_overlap_entry.set_precision(self.decimals)
+        self.ncc_overlap_entry.setWrapping(True)
+        self.ncc_overlap_entry.setRange(0.000, 99.9999)
+        self.ncc_overlap_entry.setSingleStep(0.1)
+        self.ncc_overlap_entry.setObjectName("gdb_n_overlap")
+
+        self.grid2.addWidget(nccoverlabel, 15, 0)
+        self.grid2.addWidget(self.ncc_overlap_entry, 15, 1)
+
+        # Margin
+        nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
+        nccmarginlabel.setToolTip(
+            _("Bounding box margin.")
+        )
+        self.ncc_margin_entry = FCDoubleSpinner()
+        self.ncc_margin_entry.set_precision(self.decimals)
+        self.ncc_margin_entry.set_range(-9999.9999, 9999.9999)
+        self.ncc_margin_entry.setObjectName("gdb_n_margin")
+
+        self.grid2.addWidget(nccmarginlabel, 16, 0)
+        self.grid2.addWidget(self.ncc_margin_entry, 16, 1)
+
+        # Method
+        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
+        methodlabel.setToolTip(
+            _("Algorithm for copper clearing:\n"
+              "- Standard: Fixed step inwards.\n"
+              "- Seed-based: Outwards from seed.\n"
+              "- Line-based: Parallel lines.")
+        )
+
+        self.ncc_method_combo = FCComboBox()
+        self.ncc_method_combo.addItems(
+            [_("Standard"), _("Seed"), _("Lines")]
+        )
+        self.ncc_method_combo.setObjectName("gdb_n_method")
+
+        self.grid2.addWidget(methodlabel, 17, 0)
+        self.grid2.addWidget(self.ncc_method_combo, 17, 1)
+
+        # Connect lines
+        self.ncc_connect_cb = FCCheckBox('%s' % _("Connect"))
+        self.ncc_connect_cb.setObjectName("gdb_n_connect")
+
+        self.ncc_connect_cb.setToolTip(
+            _("Draw lines between resulting\n"
+              "segments to minimize tool lifts.")
+        )
+        self.grid2.addWidget(self.ncc_connect_cb, 18, 0)
+
+        # Contour
+        self.ncc_contour_cb = FCCheckBox('%s' % _("Contour"))
+        self.ncc_contour_cb.setObjectName("gdb_n_contour")
+
+        self.ncc_contour_cb.setToolTip(
+            _("Cut around the perimeter of the polygon\n"
+              "to trim rough edges.")
+        )
+        self.grid2.addWidget(self.ncc_contour_cb, 18, 1)
+
+        # ## NCC Offset choice
+        self.ncc_choice_offset_cb = FCCheckBox('%s' % _("Offset"))
+        self.ncc_choice_offset_cb.setObjectName("gdb_n_offset")
+
+        self.ncc_choice_offset_cb.setToolTip(
+            _("If used, it will add an offset to the copper features.\n"
+              "The copper clearing will finish to a distance\n"
+              "from the copper features.\n"
+              "The value can be between 0 and 10 FlatCAM units.")
+        )
+        self.grid2.addWidget(self.ncc_choice_offset_cb, 19, 0)
+
+        # ## NCC Offset Entry
+        self.ncc_offset_spinner = FCDoubleSpinner()
+        self.ncc_offset_spinner.set_range(0.00, 10.00)
+        self.ncc_offset_spinner.set_precision(4)
+        self.ncc_offset_spinner.setWrapping(True)
+        self.ncc_offset_spinner.setObjectName("gdb_n_offset_value")
+
+        units = self.app.defaults['units'].upper()
+        if units == 'MM':
+            self.ncc_offset_spinner.setSingleStep(0.1)
+        else:
+            self.ncc_offset_spinner.setSingleStep(0.01)
+
+        self.grid2.addWidget(self.ncc_offset_spinner, 19, 1)
+
+        # ###########################################################################
+        # ############### Paint UI form #############################################
+        # ###########################################################################
+
+        self.grid3 = QtWidgets.QGridLayout()
+        self.paint_vlay.addLayout(self.grid3)
+        self.grid3.setColumnStretch(0, 0)
+        self.grid3.setColumnStretch(1, 1)
+        self.paint_vlay.addStretch()
+
+        # Overlap
+        ovlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
+        ovlabel.setToolTip(
+            _("How much (percentage) of the tool width to overlap each tool pass.\n"
+              "Adjust the value starting with lower values\n"
+              "and increasing it if areas that should be painted are still \n"
+              "not painted.\n"
+              "Lower values = faster processing, faster execution on CNC.\n"
+              "Higher values = slow processing and slow execution on CNC\n"
+              "due of too many paths.")
+        )
+        self.paintoverlap_entry = FCDoubleSpinner(suffix='%')
+        self.paintoverlap_entry.set_precision(3)
+        self.paintoverlap_entry.setWrapping(True)
+        self.paintoverlap_entry.setRange(0.0000, 99.9999)
+        self.paintoverlap_entry.setSingleStep(0.1)
+        self.paintoverlap_entry.setObjectName('gdb_p_overlap')
+
+        self.grid3.addWidget(ovlabel, 1, 0)
+        self.grid3.addWidget(self.paintoverlap_entry, 1, 1)
+
+        # Margin
+        marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
+        marginlabel.setToolTip(
+            _("Distance by which to avoid\n"
+              "the edges of the polygon to\n"
+              "be painted.")
+        )
+        self.paintmargin_entry = FCDoubleSpinner()
+        self.paintmargin_entry.set_precision(self.decimals)
+        self.paintmargin_entry.set_range(-9999.9999, 9999.9999)
+        self.paintmargin_entry.setObjectName('gdb_p_margin')
+
+        self.grid3.addWidget(marginlabel, 2, 0)
+        self.grid3.addWidget(self.paintmargin_entry, 2, 1)
+
+        # Method
+        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
+        methodlabel.setToolTip(
+            _("Algorithm for painting:\n"
+              "- Standard: Fixed step inwards.\n"
+              "- Seed-based: Outwards from seed.\n"
+              "- Line-based: Parallel lines.\n"
+              "- Laser-lines: Active only for Gerber objects.\n"
+              "Will create lines that follow the traces.\n"
+              "- Combo: In case of failure a new method will be picked from the above\n"
+              "in the order specified.")
+        )
+
+        self.paintmethod_combo = FCComboBox()
+        self.paintmethod_combo.addItems(
+            [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")]
+        )
+        idx = self.paintmethod_combo.findText(_("Laser_lines"))
+        self.paintmethod_combo.model().item(idx).setEnabled(False)
+
+        self.paintmethod_combo.setObjectName('gdb_p_method')
+
+        self.grid3.addWidget(methodlabel, 7, 0)
+        self.grid3.addWidget(self.paintmethod_combo, 7, 1)
+
+        # Connect lines
+        self.pathconnect_cb = FCCheckBox('%s' % _("Connect"))
+        self.pathconnect_cb.setObjectName('gdb_p_connect')
+        self.pathconnect_cb.setToolTip(
+            _("Draw lines between resulting\n"
+              "segments to minimize tool lifts.")
+        )
+
+        self.paintcontour_cb = FCCheckBox('%s' % _("Contour"))
+        self.paintcontour_cb.setObjectName('gdb_p_contour')
+        self.paintcontour_cb.setToolTip(
+            _("Cut around the perimeter of the polygon\n"
+              "to trim rough edges.")
+        )
+
+        self.grid3.addWidget(self.pathconnect_cb, 10, 0)
+        self.grid3.addWidget(self.paintcontour_cb, 10, 1)
+
+        # ####################################################################
+        # ####################################################################
+        # GUI for the lower part of the window
+        # ####################################################################
+        # ####################################################################
+
+        new_vlay = QtWidgets.QVBoxLayout()
+        grid_layout.addLayout(new_vlay, 1, 0, 1, 2)
+
+        self.buttons_frame = QtWidgets.QFrame()
+        self.buttons_frame.setContentsMargins(0, 0, 0, 0)
+        new_vlay.addWidget(self.buttons_frame)
+        self.buttons_box = QtWidgets.QHBoxLayout()
+        self.buttons_box.setContentsMargins(0, 0, 0, 0)
+        self.buttons_frame.setLayout(self.buttons_box)
+        self.buttons_frame.show()
+
+        add_entry_btn = FCButton(_("Add Tool in DB"))
+        add_entry_btn.setToolTip(
+            _("Add a new tool in the Tools Database.\n"
+              "It will be used in the Geometry UI.\n"
+              "You can edit it after it is added.")
+        )
+        self.buttons_box.addWidget(add_entry_btn)
+
+        # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB"))
+        # add_fct_entry_btn.setToolTip(
+        #     _("Add a new tool in the Tools Database.\n"
+        #       "It will be used in the Paint/NCC Tools UI.\n"
+        #       "You can edit it after it is added.")
+        # )
+        # self.buttons_box.addWidget(add_fct_entry_btn)
+
+        remove_entry_btn = FCButton(_("Delete Tool from DB"))
+        remove_entry_btn.setToolTip(
+            _("Remove a selection of tools in the Tools Database.")
+        )
+        self.buttons_box.addWidget(remove_entry_btn)
+
+        export_db_btn = FCButton(_("Export DB"))
+        export_db_btn.setToolTip(
+            _("Save the Tools Database to a custom text file.")
+        )
+        self.buttons_box.addWidget(export_db_btn)
+
+        import_db_btn = FCButton(_("Import DB"))
+        import_db_btn.setToolTip(
+            _("Load the Tools Database information's from a custom text file.")
+        )
+        self.buttons_box.addWidget(import_db_btn)
+
+        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
+        self.add_tool_from_db.setToolTip(
+            _("Add a new tool in the Tools Table of the\n"
+              "active Geometry object after selecting a tool\n"
+              "in the Tools Database.")
+        )
+        self.add_tool_from_db.hide()
+
+        self.cancel_tool_from_db = FCButton(_("Cancel"))
+        self.cancel_tool_from_db.hide()
+
+        hlay = QtWidgets.QHBoxLayout()
+        tree_layout.addLayout(hlay)
+        hlay.addWidget(self.add_tool_from_db)
+        hlay.addWidget(self.cancel_tool_from_db)
+        hlay.addStretch()
+
+        # ##############################################################################
+        # ##############################################################################
+        # ########## SETUP THE DICTIONARIES THAT HOLD THE WIDGETS #####################
+        # ##############################################################################
+        # ##############################################################################
+
+        self.form_fields = {
+            # Basic
+            "name":             self.name_entry,
+            "tooldia":          self.dia_entry,
+            "tool_type":        self.shape_combo,
+            "cutz":             self.cutz_entry,
+            "multidepth":       self.multidepth_cb,
+            "depthperpass":     self.multidepth_entry,
+            "travelz":          self.travelz_entry,
+            "feedrate":         self.frxy_entry,
+            "feedrate_z":       self.frz_entry,
+            "spindlespeed":     self.spindle_entry,
+            "dwell":            self.dwell_cb,
+            "dwelltime":        self.dwelltime_entry,
+
+            # Advanced
+            "type":             self.type_combo,
+            "offset":           self.tooloffset_combo,
+            "offset_value":     self.custom_offset_entry,
+            "vtipdia":          self.vdia_entry,
+            "vtipangle":        self.vangle_entry,
+            "feedrate_rapid":   self.frapids_entry,
+            "extracut":         self.ecut_cb,
+            "extracut_length":  self.ecut_length_entry,
+
+            # NCC
+            "tools_nccoperation":       self.op_radio,
+            "tools_nccmilling_type":    self.milling_type_radio,
+            "tools_nccoverlap":         self.ncc_overlap_entry,
+            "tools_nccmargin":          self.ncc_margin_entry,
+            "tools_nccmethod":          self.ncc_method_combo,
+            "tools_nccconnect":         self.ncc_connect_cb,
+            "tools_ncccontour":         self.ncc_contour_cb,
+            "tools_ncc_offset_choice":  self.ncc_choice_offset_cb,
+            "tools_ncc_offset_value":   self.ncc_offset_spinner,
+
+            # Paint
+            "tools_paintoverlap":       self.paintoverlap_entry,
+            "tools_paintmargin":        self.paintmargin_entry,
+            "tools_paintmethod":        self.paintmethod_combo,
+            "tools_pathconnect":        self.pathconnect_cb,
+            "tools_paintcontour":       self.paintcontour_cb,
+        }
+
+        self.name2option = {
+            # Basic
+            "gdb_name":             "name",
+            "gdb_dia":              "tooldia",
+            "gdb_shape":            "tool_type",
+            "gdb_cutz":             "cutz",
+            "gdb_multidepth":       "multidepth",
+            "gdb_multidepth_entry": "depthperpass",
+            "gdb_travel":           "travelz",
+            "gdb_frxy":             "feedrate",
+            "gdb_frz":              "feedrate_z",
+            "gdb_spindle":          "spindlespeed",
+            "gdb_dwell":            "dwell",
+            "gdb_dwelltime":        "dwelltime",
+
+            # Advanced
+            "gdb_type":             "type",
+            "gdb_tool_offset":      "offset",
+            "gdb_custom_offset":    "offset_value",
+            "gdb_vdia":             "vtipdia",
+            "gdb_vangle":           "vtipangle",
+            "gdb_frapids":          "feedrate_rapid",
+            "gdb_ecut":             "extracut",
+            "gdb_ecut_length":      "extracut_length",
+
+            # NCC
+            "gdb_n_operation":      "tools_nccoperation",
+            "gdb_n_overlap":        "tools_nccoverlap",
+            "gdb_n_margin":         "tools_nccmargin",
+            "gdb_n_method":         "tools_nccmethod",
+            "gdb_n_connect":        "tools_nccconnect",
+            "gdb_n_contour":        "tools_ncccontour",
+            "gdb_n_offset":         "tools_ncc_offset_choice",
+            "gdb_n_offset_value":   "tools_ncc_offset_value",
+            "gdb_n_milling_type":   "tools_nccmilling_type",
+
+            # Paint
+            'gdb_p_overlap':        "tools_paintoverlap",
+            'gdb_p_margin':         "tools_paintmargin",
+            'gdb_p_method':         "tools_paintmethod",
+            'gdb_p_connect':        "tools_pathconnect",
+            'gdb_p_contour':        "tools_paintcontour",
+        }
+
+        self.current_toolid = None
+
+        # variable to show if double clicking and item will trigger adding a tool from DB
+        self.ok_to_add = False
+
+        # ##############################################################################
+        # ######################## SIGNALS #############################################
+        # ##############################################################################
+
+        add_entry_btn.clicked.connect(self.on_tool_add)
+        remove_entry_btn.clicked.connect(self.on_tool_delete)
+        export_db_btn.clicked.connect(self.on_export_tools_db_file)
+        import_db_btn.clicked.connect(self.on_import_tools_db_file)
+        # closebtn.clicked.connect(self.accept)
+
+        self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app)
+        self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool)
+
+        # self.tree_widget.selectionModel().selectionChanged.connect(self.on_list_selection_change)
+        self.tree_widget.currentItemChanged.connect(self.on_list_selection_change)
+        self.tree_widget.itemChanged.connect(self.on_list_item_edited)
+        self.tree_widget.customContextMenuRequested.connect(self.on_menu_request)
+
+        self.tree_widget.itemDoubleClicked.connect(self.on_item_double_clicked)
+
+        self.setup_db_ui()
+
+    def on_menu_request(self, pos):
+
+        menu = QtWidgets.QMenu()
+        add_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/plus16.png'), _("Add to DB"))
+        add_tool.triggered.connect(self.on_tool_add)
+
+        copy_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/copy16.png'), _("Copy from DB"))
+        copy_tool.triggered.connect(self.on_tool_copy)
+
+        delete_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete from DB"))
+        delete_tool.triggered.connect(self.on_tool_delete)
+
+        # tree_item = self.tree_widget.itemAt(pos)
+        menu.exec(self.tree_widget.viewport().mapToGlobal(pos))
+
+    def on_item_double_clicked(self, item, column):
+        if column == 0 and self.ok_to_add is True:
+            self.ok_to_add = False
+            self.on_tool_requested_from_app()
+
+    def on_list_selection_change(self, current, previous):
+        # for idx in current.indexes():
+        #     print(idx.data())
+        # print(current.text(0))
+        self.current_toolid = int(current.text(0))
+
+        self.storage_to_form(self.db_tool_dict[current.text(0)])
+
+    def on_list_item_edited(self, item, column):
+        if column == 0:
+            return
+
+        self.name_entry.set_value(item.text(1))
+
+    def storage_to_form(self, dict_storage):
+        for form_key in self.form_fields:
+            for storage_key in dict_storage:
+                if form_key == storage_key:
+                    try:
+                        self.form_fields[form_key].set_value(dict_storage[form_key])
+                    except Exception as e:
+                        print(str(e))
+                if storage_key == 'data':
+                    for data_key in dict_storage[storage_key]:
+                        if form_key == data_key:
+                            try:
+                                self.form_fields[form_key].set_value(dict_storage['data'][data_key])
+                            except Exception as e:
+                                print(str(e))
+
+    def form_to_storage(self, tool):
+        self.blockSignals(True)
+
+        widget_changed = self.sender()
+        wdg_objname = widget_changed.objectName()
+        option_changed = self.name2option[wdg_objname]
+
+        tooluid_item = int(tool)
+
+        for tooluid_key, tooluid_val in self.db_tool_dict.items():
+            if int(tooluid_key) == tooluid_item:
+                new_option_value = self.form_fields[option_changed].get_value()
+                if option_changed in tooluid_val:
+                    tooluid_val[option_changed] = new_option_value
+                if option_changed in tooluid_val['data']:
+                    tooluid_val['data'][option_changed] = new_option_value
+
+        self.blockSignals(False)
+
+    def setup_db_ui(self):
+        filename = self.app.data_path + '/geo_tools_db.FlatDB'
+
+        # load the database tools from the file
+        try:
+            with open(filename) as f:
+                tools = f.read()
+        except IOError:
+            self.app.log.error("Could not load tools DB file.")
+            self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
+            return
+
+        try:
+            self.db_tool_dict = json.loads(tools)
+        except Exception:
+            e = sys.exc_info()[0]
+            self.app.log.error(str(e))
+            self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
+            return
+
+        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+
+        self.build_db_ui()
+
+    def build_db_ui(self):
+        self.ui_disconnect()
+        nr_crt = 0
+
+        parent = self.tree_widget
+        self.tree_widget.blockSignals(True)
+        self.tree_widget.clear()
+        self.tree_widget.blockSignals(False)
+
+        for toolid, dict_val in self.db_tool_dict.items():
+            row = nr_crt
+            nr_crt += 1
+
+            t_name = dict_val['name']
+            try:
+                # self.add_tool_table_line(row, name=t_name, tooldict=dict_val)
+                self.tree_widget.blockSignals(True)
+                try:
+                    self.tree_widget.addParentEditable(parent=parent, title=[str(row+1), t_name], editable=True)
+                except Exception as e:
+                    print('FlatCAMCoomn.ToolDB2.build_db_ui() -> ', str(e))
+                self.tree_widget.blockSignals(False)
+            except Exception as e:
+                self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
+
+        if self.current_toolid is None or self.current_toolid < 1:
+            if self.db_tool_dict:
+                self.storage_to_form(self.db_tool_dict['1'])
+
+                # Enable GUI
+                self.basic_box.setEnabled(True)
+                self.advanced_box.setEnabled(True)
+                self.ncc_box.setEnabled(True)
+                self.paint_box.setEnabled(True)
+
+                self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0))
+                # self.tree_widget.setFocus()
+
+            else:
+                # Disable GUI
+                self.basic_box.setEnabled(False)
+                self.advanced_box.setEnabled(False)
+                self.ncc_box.setEnabled(False)
+                self.paint_box.setEnabled(False)
+        else:
+            self.storage_to_form(self.db_tool_dict[str(self.current_toolid)])
+
+        self.ui_connect()
+
+    def on_tool_add(self):
+        """
+        Add a tool in the DB Tool Table
+        :return: None
+        """
+
+        default_data = {}
+        default_data.update({
+            "plot":             True,
+            "cutz":             float(self.app.defaults["geometry_cutz"]),
+            "multidepth":       self.app.defaults["geometry_multidepth"],
+            "depthperpass":     float(self.app.defaults["geometry_depthperpass"]),
+            "vtipdia":          float(self.app.defaults["geometry_vtipdia"]),
+            "vtipangle":        float(self.app.defaults["geometry_vtipangle"]),
+            "travelz":          float(self.app.defaults["geometry_travelz"]),
+            "feedrate":         float(self.app.defaults["geometry_feedrate"]),
+            "feedrate_z":       float(self.app.defaults["geometry_feedrate_z"]),
+            "feedrate_rapid":   float(self.app.defaults["geometry_feedrate_rapid"]),
+            "spindlespeed":     self.app.defaults["geometry_spindlespeed"],
+            "dwell":            self.app.defaults["geometry_dwell"],
+            "dwelltime":        float(self.app.defaults["geometry_dwelltime"]),
+            "ppname_g":         self.app.defaults["geometry_ppname_g"],
+            "extracut":         self.app.defaults["geometry_extracut"],
+            "extracut_length":  float(self.app.defaults["geometry_extracut_length"]),
+            "toolchange":       self.app.defaults["geometry_toolchange"],
+            "toolchangexy":     self.app.defaults["geometry_toolchangexy"],
+            "toolchangez":      float(self.app.defaults["geometry_toolchangez"]),
+            "startz":           self.app.defaults["geometry_startz"],
+            "endz":             float(self.app.defaults["geometry_endz"]),
+
+            # NCC
+            "tools_nccoperation":       self.app.defaults["tools_nccoperation"],
+            "tools_nccmilling_type":    self.app.defaults["tools_nccmilling_type"],
+            "tools_nccoverlap":         float(self.app.defaults["tools_nccoverlap"]),
+            "tools_nccmargin":          float(self.app.defaults["tools_nccmargin"]),
+            "tools_nccmethod":          self.app.defaults["tools_nccmethod"],
+            "tools_nccconnect":         self.app.defaults["tools_nccconnect"],
+            "tools_ncccontour":         self.app.defaults["tools_ncccontour"],
+            "tools_ncc_offset_choice":  self.app.defaults["tools_ncc_offset_choice"],
+            "tools_ncc_offset_value":   float(self.app.defaults["tools_ncc_offset_value"]),
+
+            # Paint
+            "tools_paintoverlap":       float(self.app.defaults["tools_paintoverlap"]),
+            "tools_paintmargin":        float(self.app.defaults["tools_paintmargin"]),
+            "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
+            "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
+            "tools_paintcontour":       self.app.defaults["tools_paintcontour"],
+        })
+
+        dict_elem = {}
+        dict_elem['name'] = 'new_tool'
+        if type(self.app.defaults["geometry_cnctooldia"]) == float:
+            dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
+        else:
+            try:
+                tools_string = self.app.defaults["geometry_cnctooldia"].split(",")
+                tools_diameters = [eval(a) for a in tools_string if a != '']
+                dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0
+            except Exception as e:
+                self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e))
+                return
+
+        dict_elem['offset'] = 'Path'
+        dict_elem['offset_value'] = 0.0
+        dict_elem['type'] = 'Rough'
+        dict_elem['tool_type'] = 'C1'
+        dict_elem['data'] = default_data
+
+        new_toolid = len(self.db_tool_dict) + 1
+        self.db_tool_dict[str(new_toolid)] = deepcopy(dict_elem)
+
+        # add the new entry to the Tools DB table
+        self.update_storage()
+        self.build_db_ui()
+        self.app.inform.emit('[success] %s' % _("Tool added to DB."))
+
+    def on_tool_copy(self):
+        """
+        Copy a selection of Tools in the Tools DB table
+        :return:
+        """
+        new_tool_id = len(self.db_tool_dict)
+        for item in self.tree_widget.selectedItems():
+            old_tool_id = item.data(0, QtCore.Qt.DisplayRole)
+
+            for toolid, dict_val in list(self.db_tool_dict.items()):
+                if int(old_tool_id) == int(toolid):
+                    new_tool_id += 1
+                    new_key = str(new_tool_id)
+
+                    self.db_tool_dict.update({
+                        new_key: deepcopy(dict_val)
+                    })
+
+        self.current_toolid = new_tool_id
+
+        self.update_storage()
+        self.build_db_ui()
+        self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
+
+    def on_tool_delete(self):
+        """
+        Delete a selection of Tools in the Tools DB table
+        :return:
+        """
+        for item in self.tree_widget.selectedItems():
+            toolname_to_remove = item.data(0, QtCore.Qt.DisplayRole)
+
+            for toolid, dict_val in list(self.db_tool_dict.items()):
+                if int(toolname_to_remove) == int(toolid):
+                    # remove from the storage
+                    self.db_tool_dict.pop(toolid, None)
+
+        self.current_toolid -= 1
+
+        self.update_storage()
+        self.build_db_ui()
+        self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB."))
+
+    def on_export_tools_db_file(self):
+        self.app.report_usage("on_export_tools_db_file")
+        self.app.log.debug("on_export_tools_db_file()")
+
+        date = str(datetime.today()).rpartition('.')[0]
+        date = ''.join(c for c in date if c not in ':-')
+        date = date.replace(' ', '_')
+
+        filter__ = "Text File (*.TXT);;All Files (*.*)"
+        filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Tools Database"),
+                                                           directory='{l_save}/FlatCAM_{n}_{date}'.format(
+                                                                l_save=str(self.app.get_last_save_folder()),
+                                                                n=_("Tools_Database"),
+                                                                date=date),
+                                                           filter=filter__)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+            return
+        else:
+            try:
+                f = open(filename, 'w')
+                f.close()
+            except PermissionError:
+                self.app.inform.emit('[WARNING] %s' %
+                                     _("Permission denied, saving not possible.\n"
+                                       "Most likely another app is holding the file open and not accessible."))
+                return
+            except IOError:
+                self.app.log.debug('Creating a new Tools DB file ...')
+                f = open(filename, 'w')
+                f.close()
+            except Exception:
+                e = sys.exc_info()[0]
+                self.app.log.error("Could not load Tools DB file.")
+                self.app.log.error(str(e))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
+                return
+
+            # Save update options
+            try:
+                # Save Tools DB in a file
+                try:
+                    with open(filename, "w") as f:
+                        json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
+                except Exception as e:
+                    self.app.log.debug("App.on_save_tools_db() --> %s" % str(e))
+                    self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                    return
+            except Exception:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                return
+
+        self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename))
+
+    def on_import_tools_db_file(self):
+        self.app.report_usage("on_import_tools_db_file")
+        self.app.log.debug("on_import_tools_db_file()")
+
+        filter__ = "Text File (*.TXT);;All Files (*.*)"
+        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+        else:
+            try:
+                with open(filename) as f:
+                    tools_in_db = f.read()
+            except IOError:
+                self.app.log.error("Could not load Tools DB file.")
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
+                return
+
+            try:
+                self.db_tool_dict = json.loads(tools_in_db)
+            except Exception:
+                e = sys.exc_info()[0]
+                self.app.log.error(str(e))
+                self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
+                return
+
+            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+            self.build_db_ui()
+            self.update_storage()
+
+    def on_save_tools_db(self, silent=False):
+        self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.")
+
+        filename = self.app.data_path + "/geo_tools_db.FlatDB"
+
+        # Preferences save, update the color of the Tools DB Tab text
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
+
+                # Save Tools DB in a file
+                try:
+                    f = open(filename, "w")
+                    json.dump(self.db_tool_dict, f, default=to_dict, indent=2)
+                    f.close()
+                except Exception as e:
+                    self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e))
+                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file."))
+                    return
+
+                if not silent:
+                    self.app.inform.emit('[success] %s' % _("Saved Tools DB."))
+
+    def ui_connect(self):
+        # make sure that we don't make multiple connections to the widgets
+        self.ui_disconnect()
+
+        self.name_entry.editingFinished.connect(self.update_tree_name)
+
+        for key in self.form_fields:
+            wdg = self.form_fields[key]
+
+            # FCEntry
+            if isinstance(wdg, FCEntry):
+                wdg.textChanged.connect(self.update_storage)
+
+            # ComboBox
+            if isinstance(wdg, FCComboBox):
+                wdg.currentIndexChanged.connect(self.update_storage)
+
+            # CheckBox
+            if isinstance(wdg, FCCheckBox):
+                wdg.toggled.connect(self.update_storage)
+
+            # FCRadio
+            if isinstance(wdg, RadioSet):
+                wdg.activated_custom.connect(self.update_storage)
+
+            # SpinBox, DoubleSpinBox
+            if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
+                wdg.valueChanged.connect(self.update_storage)
+
+    def ui_disconnect(self):
+        try:
+            self.name_entry.editingFinished.disconnect(self.update_tree_name)
+        except (TypeError, AttributeError):
+            pass
+
+        for key in self.form_fields:
+            wdg = self.form_fields[key]
+
+            # FCEntry
+            if isinstance(wdg, FCEntry):
+                try:
+                    wdg.textChanged.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+            # ComboBox
+            if isinstance(wdg, FCComboBox):
+                try:
+                    wdg.currentIndexChanged.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+            # CheckBox
+            if isinstance(wdg, FCCheckBox):
+                try:
+                    wdg.toggled.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+            # FCRadio
+            if isinstance(wdg, RadioSet):
+                try:
+                    wdg.activated_custom.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+            # SpinBox, DoubleSpinBox
+            if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
+                try:
+                    wdg.valueChanged.disconnect(self.update_storage)
+                except (TypeError, AttributeError):
+                    pass
+
+    def update_tree_name(self):
+        val = self.name_entry.get_value()
+
+        item = self.tree_widget.currentItem()
+        # I'm setting the value for the second column (designated by 1) because first column holds the ID
+        # and second column holds the Name (this behavior is set in the build_ui method)
+        item.setData(1, QtCore.Qt.DisplayRole, val)
+
+    def update_storage(self):
+        """
+        Update the dictionary that is the storage of the tools 'database'
+        :return:
+        """
+        tool_id = str(self.current_toolid)
+
+        try:
+            wdg = self.sender()
+
+            assert isinstance(wdg, QtWidgets.QWidget), "Expected a QWidget got %s" % type(wdg)
+
+            if wdg is None:
+                return
+
+            wdg_name = wdg.objectName()
+            val = wdg.get_value()
+        except AttributeError:
+            return
+
+        if wdg_name == "gdb_name":
+            self.db_tool_dict[tool_id]['name'] = val
+        elif wdg_name == "gdb_dia":
+            self.db_tool_dict[tool_id]['tooldia'] = val
+        elif wdg_name == "gdb_tool_offset":
+            self.db_tool_dict[tool_id]['offset'] = val
+        elif wdg_name == "gdb_custom_offset":
+            self.db_tool_dict[tool_id]['offset_value'] = val
+        elif wdg_name == "gdb_type":
+            self.db_tool_dict[tool_id]['type'] = val
+        elif wdg_name == "gdb_shape":
+            self.db_tool_dict[tool_id]['tool_type'] = val
+        else:
+            if wdg_name == "gdb_cutz":
+                self.db_tool_dict[tool_id]['data']['cutz'] = val
+            elif wdg_name == "gdb_multidepth":
+                self.db_tool_dict[tool_id]['data']['multidepth'] = val
+            elif wdg_name == "gdb_multidepth_entry":
+                self.db_tool_dict[tool_id]['data']['depthperpass'] = val
+
+            elif wdg_name == "gdb_travel":
+                self.db_tool_dict[tool_id]['data']['travelz'] = val
+            elif wdg_name == "gdb_frxy":
+                self.db_tool_dict[tool_id]['data']['feedrate'] = val
+            elif wdg_name == "gdb_frz":
+                self.db_tool_dict[tool_id]['data']['feedrate_z'] = val
+            elif wdg_name == "gdb_spindle":
+                self.db_tool_dict[tool_id]['data']['spindlespeed'] = val
+            elif wdg_name == "gdb_dwell":
+                self.db_tool_dict[tool_id]['data']['dwell'] = val
+            elif wdg_name == "gdb_dwelltime":
+                self.db_tool_dict[tool_id]['data']['dwelltime'] = val
+
+            elif wdg_name == "gdb_vdia":
+                self.db_tool_dict[tool_id]['data']['vtipdia'] = val
+            elif wdg_name == "gdb_vangle":
+                self.db_tool_dict[tool_id]['data']['vtipangle'] = val
+            elif wdg_name == "gdb_frapids":
+                self.db_tool_dict[tool_id]['data']['feedrate_rapid'] = val
+            elif wdg_name == "gdb_ecut":
+                self.db_tool_dict[tool_id]['data']['extracut'] = val
+            elif wdg_name == "gdb_ecut_length":
+                self.db_tool_dict[tool_id]['data']['extracut_length'] = val
+
+            # NCC Tool
+            elif wdg_name == "gdb_n_operation":
+                self.db_tool_dict[tool_id]['data']['tools_nccoperation'] = val
+            elif wdg_name == "gdb_n_overlap":
+                self.db_tool_dict[tool_id]['data']['tools_nccoverlap'] = val
+            elif wdg_name == "gdb_n_margin":
+                self.db_tool_dict[tool_id]['data']['tools_nccmargin'] = val
+            elif wdg_name == "gdb_n_method":
+                self.db_tool_dict[tool_id]['data']['tools_nccmethod'] = val
+            elif wdg_name == "gdb_n_connect":
+                self.db_tool_dict[tool_id]['data']['tools_nccconnect'] = val
+            elif wdg_name == "gdb_n_contour":
+                self.db_tool_dict[tool_id]['data']['tools_ncccontour'] = val
+            elif wdg_name == "gdb_n_offset":
+                self.db_tool_dict[tool_id]['data']['tools_ncc_offset_choice'] = val
+            elif wdg_name == "gdb_n_offset_value":
+                self.db_tool_dict[tool_id]['data']['tools_ncc_offset_value'] = val
+            elif wdg_name == "gdb_n_milling_type":
+                self.db_tool_dict[tool_id]['data']['tools_nccmilling_type'] = val
+
+            # Paint Tool
+            elif wdg_name == "gdb_p_overlap":
+                self.db_tool_dict[tool_id]['data']['tools_paintoverlap'] = val
+            elif wdg_name == "gdb_p_margin":
+                self.db_tool_dict[tool_id]['data']['tools_paintmargin'] = val
+            elif wdg_name == "gdb_p_method":
+                self.db_tool_dict[tool_id]['data']['tools_paintmethod'] = val
+            elif wdg_name == "gdb_p_connect":
+                self.db_tool_dict[tool_id]['data']['tools_pathconnect'] = val
+            elif wdg_name == "gdb_p_contour":
+                self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val
+
+        self.callback_app()
+
+    def on_tool_requested_from_app(self):
+        if not self.tree_widget.selectedItems():
+            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table"))
+            return
+
+        for item in self.tree_widget.selectedItems():
+            tool_uid = item.data(0, QtCore.Qt.DisplayRole)
+
+            for key in self.db_tool_dict.keys():
+                if str(key) == str(tool_uid):
+                    selected_tool = self.db_tool_dict[key]
+                    self.on_tool_request(tool=selected_tool)
+
+    def on_cancel_tool(self):
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                wdg = self.app.ui.plot_tab_area.widget(idx)
+                wdg.deleteLater()
+                self.app.ui.plot_tab_area.removeTab(idx)
+        self.app.inform.emit('%s' % _("Cancelled adding tool from DB."))
+
+    # def resize_new_tool_table_widget(self, min_size, max_size):
+    #     """
+    #     Resize the table widget responsible for adding new tool in the Tool Database
+    #
+    #     :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
+    #     :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar()
+    #     :return:
+    #     """
+    #     t_height = self.t_height
+    #     if max_size > min_size:
+    #         t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height()
+    #
+    #     self.new_tool_table_widget.setMaximumHeight(t_height)
+
+    def closeEvent(self, QCloseEvent):
+        super().closeEvent(QCloseEvent)

+ 17 - 16
FlatCAMObj.py

@@ -31,7 +31,6 @@ from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
 from flatcamParsers.ParseExcellon import Excellon
 from flatcamParsers.ParseExcellon import Excellon
 from flatcamParsers.ParseGerber import Gerber
 from flatcamParsers.ParseGerber import Gerber
 from camlib import Geometry, CNCjob
 from camlib import Geometry, CNCjob
-import FlatCAMApp
 
 
 # from flatcamGUI.VisPyVisuals import ShapeCollection
 # from flatcamGUI.VisPyVisuals import ShapeCollection
 
 
@@ -224,7 +223,7 @@ class FlatCAMObj(QtCore.QObject):
         """
         """
 
 
         self.muted_ui = True
         self.muted_ui = True
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()")
+        log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()")
 
 
         try:
         try:
             # HACK: disconnect the scale entry signal since on focus out event will trigger an undesired scale()
             # HACK: disconnect the scale entry signal since on focus out event will trigger an undesired scale()
@@ -334,7 +333,7 @@ class FlatCAMObj(QtCore.QObject):
 
 
         :return: None
         :return: None
         """
         """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.to_form()")
+        log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.to_form()")
         for option in self.options:
         for option in self.options:
             try:
             try:
                 self.set_form_item(option)
                 self.set_form_item(option)
@@ -348,7 +347,7 @@ class FlatCAMObj(QtCore.QObject):
         :return: None
         :return: None
         :rtype: None
         :rtype: None
         """
         """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()")
+        log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()")
         for option in self.options:
         for option in self.options:
             try:
             try:
                 self.read_form_item(option)
                 self.read_form_item(option)
@@ -392,7 +391,7 @@ class FlatCAMObj(QtCore.QObject):
         :param kind:    Used by only some of the FlatCAM objects
         :param kind:    Used by only some of the FlatCAM objects
         :return:        Whether to continue plotting or not depending on the "plot" option. Boolean
         :return:        Whether to continue plotting or not depending on the "plot" option. Boolean
         """
         """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()")
+        log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()")
 
 
         if self.deleted:
         if self.deleted:
             return False
             return False
@@ -691,7 +690,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         :return: None
         :return: None
         """
         """
         FlatCAMObj.set_ui(self, ui)
         FlatCAMObj.set_ui(self, ui)
-        FlatCAMApp.App.log.debug("FlatCAMGerber.set_ui()")
+        log.debug("FlatCAMGerber.set_ui()")
 
 
         self.units = self.app.defaults['units'].upper()
         self.units = self.app.defaults['units'].upper()
 
 
@@ -1768,7 +1767,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         :param kwargs:  Color and face_color, visible
         :param kwargs:  Color and face_color, visible
         :return:
         :return:
         """
         """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot()")
+        log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot()")
 
 
         # Does all the required setup and returns False
         # Does all the required setup and returns False
         # if the 'ptint' option is set to False.
         # if the 'ptint' option is set to False.
@@ -1871,7 +1870,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         :return:
         :return:
         """
         """
 
 
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot_aperture()")
+        log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot_aperture()")
 
 
         # Does all the required setup and returns False
         # Does all the required setup and returns False
         # if the 'ptint' option is set to False.
         # if the 'ptint' option is set to False.
@@ -1961,8 +1960,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         """
         """
 
 
         self.ui_disconnect()
         self.ui_disconnect()
-        cw = self.sender()
         try:
         try:
+            cw = self.sender()
+            assert isinstance(cw, FCCheckBox),\
+                "Expected a cellWidget but got %s" % type(cw)
             cw_index = self.ui.apertures_table.indexAt(cw.pos())
             cw_index = self.ui.apertures_table.indexAt(cw.pos())
             cw_row = cw_index.row()
             cw_row = cw_index.row()
         except AttributeError:
         except AttributeError:
@@ -2870,7 +2871,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
         """
         """
         FlatCAMObj.set_ui(self, ui)
         FlatCAMObj.set_ui(self, ui)
 
 
-        FlatCAMApp.App.log.debug("FlatCAMExcellon.set_ui()")
+        log.debug("FlatCAMExcellon.set_ui()")
 
 
         self.units = self.app.defaults['units'].upper()
         self.units = self.app.defaults['units'].upper()
 
 
@@ -6570,7 +6571,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
                  feedrate=3.0, feedrate_rapid=3.0, z_cut=-0.002, tooldia=0.0,
                  feedrate=3.0, feedrate_rapid=3.0, z_cut=-0.002, tooldia=0.0,
                  spindlespeed=None):
                  spindlespeed=None):
 
 
-        FlatCAMApp.App.log.debug("Creating CNCJob object...")
+        log.debug("Creating CNCJob object...")
 
 
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
@@ -6882,7 +6883,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
     def set_ui(self, ui):
     def set_ui(self, ui):
         FlatCAMObj.set_ui(self, ui)
         FlatCAMObj.set_ui(self, ui)
 
 
-        FlatCAMApp.App.log.debug("FlatCAMCNCJob.set_ui()")
+        log.debug("FlatCAMCNCJob.set_ui()")
 
 
         assert isinstance(self.ui, CNCObjectUI), \
         assert isinstance(self.ui, CNCObjectUI), \
             "Expected a CNCObjectUI, got %s" % type(self.ui)
             "Expected a CNCObjectUI, got %s" % type(self.ui)
@@ -7756,7 +7757,7 @@ class FlatCAMScript(FlatCAMObj):
     def __init__(self, name):
     def __init__(self, name):
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
-        FlatCAMApp.App.log.debug("Creating a FlatCAMScript object...")
+        log.debug("Creating a FlatCAMScript object...")
         FlatCAMObj.__init__(self, name)
         FlatCAMObj.__init__(self, name)
 
 
         self.kind = "script"
         self.kind = "script"
@@ -7785,7 +7786,7 @@ class FlatCAMScript(FlatCAMObj):
         :return:
         :return:
         """
         """
         FlatCAMObj.set_ui(self, ui)
         FlatCAMObj.set_ui(self, ui)
-        FlatCAMApp.App.log.debug("FlatCAMScript.set_ui()")
+        log.debug("FlatCAMScript.set_ui()")
 
 
         assert isinstance(self.ui, ScriptObjectUI), \
         assert isinstance(self.ui, ScriptObjectUI), \
             "Expected a ScriptObjectUI, got %s" % type(self.ui)
             "Expected a ScriptObjectUI, got %s" % type(self.ui)
@@ -7960,7 +7961,7 @@ class FlatCAMDocument(FlatCAMObj):
     def __init__(self, name):
     def __init__(self, name):
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
-        FlatCAMApp.App.log.debug("Creating a Document object...")
+        log.debug("Creating a Document object...")
         FlatCAMObj.__init__(self, name)
         FlatCAMObj.__init__(self, name)
 
 
         self.kind = "document"
         self.kind = "document"
@@ -7982,7 +7983,7 @@ class FlatCAMDocument(FlatCAMObj):
 
 
     def set_ui(self, ui):
     def set_ui(self, ui):
         FlatCAMObj.set_ui(self, ui)
         FlatCAMObj.set_ui(self, ui)
-        FlatCAMApp.App.log.debug("FlatCAMDocument.set_ui()")
+        log.debug("FlatCAMDocument.set_ui()")
 
 
         assert isinstance(self.ui, DocumentObjectUI), \
         assert isinstance(self.ui, DocumentObjectUI), \
             "Expected a DocumentObjectUI, got %s" % type(self.ui)
             "Expected a DocumentObjectUI, got %s" % type(self.ui)

+ 4 - 2
FlatCAMPostProc.py

@@ -12,8 +12,10 @@ from abc import ABCMeta, abstractmethod
 import math
 import math
 
 
 # module-root dictionary of preprocessors
 # module-root dictionary of preprocessors
-import FlatCAMApp
 
 
+import logging
+
+log = logging.getLogger('base')
 preprocessors = {}
 preprocessors = {}
 
 
 
 
@@ -23,7 +25,7 @@ class ABCPostProcRegister(ABCMeta):
         newclass = super(ABCPostProcRegister, cls).__new__(cls, clsname, bases, attrs)
         newclass = super(ABCPostProcRegister, cls).__new__(cls, clsname, bases, attrs)
         if object not in bases:
         if object not in bases:
             if newclass.__name__ in preprocessors:
             if newclass.__name__ in preprocessors:
-                FlatCAMApp.App.log.warning('Preprocessor %s has been overriden' % newclass.__name__)
+                log.warning('Preprocessor %s has been overriden' % newclass.__name__)
             preprocessors[newclass.__name__] = newclass()  # here is your register function
             preprocessors[newclass.__name__] = newclass()  # here is your register function
         return newclass
         return newclass
 
 

+ 22 - 16
ObjectCollection.py

@@ -19,7 +19,6 @@ from PyQt5.QtGui import QColor
 from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon, FlatCAMCNCjob, FlatCAMDocument, FlatCAMScript, \
 from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon, FlatCAMCNCjob, FlatCAMDocument, FlatCAMScript, \
     FlatCAMObj
     FlatCAMObj
 import inspect  # TODO: Remove
 import inspect  # TODO: Remove
-import FlatCAMApp
 
 
 import re
 import re
 import logging
 import logging
@@ -332,7 +331,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         self.update_list_signal.connect(self.on_update_list_signal)
         self.update_list_signal.connect(self.on_update_list_signal)
 
 
     def promise(self, obj_name):
     def promise(self, obj_name):
-        FlatCAMApp.App.log.debug("Object %s has been promised." % obj_name)
+        log.debug("Object %s has been promised." % obj_name)
         self.promises.add(obj_name)
         self.promises.add(obj_name)
 
 
     def has_promises(self):
     def has_promises(self):
@@ -349,7 +348,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         return len(self.plot_promises) > 0
         return len(self.plot_promises) > 0
 
 
     def on_mouse_down(self, event):
     def on_mouse_down(self, event):
-        FlatCAMApp.App.log.debug("Mouse button pressed on list")
+        log.debug("Mouse button pressed on list")
 
 
     def on_menu_request(self, pos):
     def on_menu_request(self, pos):
 
 
@@ -532,21 +531,21 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         # return QtWidgets.QAbstractItemModel.flags(self, index)
         # return QtWidgets.QAbstractItemModel.flags(self, index)
 
 
     def append(self, obj, active=False, to_index=None):
     def append(self, obj, active=False, to_index=None):
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
+        log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
 
 
         name = obj.options["name"]
         name = obj.options["name"]
 
 
         # Check promises and clear if exists
         # Check promises and clear if exists
         if name in self.promises:
         if name in self.promises:
             self.promises.remove(name)
             self.promises.remove(name)
-            # FlatCAMApp.App.log.debug("Promised object %s became available." % name)
-            # FlatCAMApp.App.log.debug("%d promised objects remaining." % len(self.promises))
+            # log.debug("Promised object %s became available." % name)
+            # log.debug("%d promised objects remaining." % len(self.promises))
 
 
         # Prevent same name
         # Prevent same name
         while name in self.get_names():
         while name in self.get_names():
             # ## Create a new name
             # ## Create a new name
             # Ends with number?
             # Ends with number?
-            FlatCAMApp.App.log.debug("new_object(): Object name (%s) exists, changing." % name)
+            log.debug("new_object(): Object name (%s) exists, changing." % name)
             match = re.search(r'(.*[^\d])?(\d+)$', name)
             match = re.search(r'(.*[^\d])?(\d+)$', name)
             if match:  # Yes: Increment the number!
             if match:  # Yes: Increment the number!
                 base = match.group(1) or ''
                 base = match.group(1) or ''
@@ -596,7 +595,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         :rtype: list
         :rtype: list
         """
         """
 
 
-        # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
+        # log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
         return [x.options['name'] for x in self.get_list()]
         return [x.options['name'] for x in self.get_list()]
 
 
     def get_bounds(self):
     def get_bounds(self):
@@ -606,7 +605,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         :return: [xmin, ymin, xmax, ymax]
         :return: [xmin, ymin, xmax, ymax]
         :rtype: list
         :rtype: list
         """
         """
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()")
+        log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()")
 
 
         # TODO: Move the operation out of here.
         # TODO: Move the operation out of here.
 
 
@@ -624,7 +623,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                 xmax = max([xmax, gxmax])
                 xmax = max([xmax, gxmax])
                 ymax = max([ymax, gymax])
                 ymax = max([ymax, gymax])
             except Exception as e:
             except Exception as e:
-                FlatCAMApp.App.log.warning("DEV WARNING: Tried to get bounds of empty geometry. %s" % str(e))
+                log.warning("DEV WARNING: Tried to get bounds of empty geometry. %s" % str(e))
 
 
         return [xmin, ymin, xmax, ymax]
         return [xmin, ymin, xmax, ymax]
 
 
@@ -638,7 +637,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         :return: The requested object or None if no such object.
         :return: The requested object or None if no such object.
         :rtype: FlatCAMObj or None
         :rtype: FlatCAMObj or None
         """
         """
-        # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()")
+        # log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()")
 
 
         if isCaseSensitive is None or isCaseSensitive is True:
         if isCaseSensitive is None or isCaseSensitive is True:
             for obj in self.get_list():
             for obj in self.get_list():
@@ -760,7 +759,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         self.app.all_objects_list = self.get_list()
         self.app.all_objects_list = self.get_list()
 
 
     def delete_all(self):
     def delete_all(self):
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
+        log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
 
 
         self.app.object_status_changed.emit(None, 'delete_all', '')
         self.app.object_status_changed.emit(None, 'delete_all', '')
 
 
@@ -897,8 +896,15 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             self.set_inactive(name)
             self.set_inactive(name)
 
 
     def on_list_selection_change(self, current, previous):
     def on_list_selection_change(self, current, previous):
-        # FlatCAMApp.App.log.debug("on_list_selection_change()")
-        # FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
+        """
+
+        :param current:     Current selected item
+        :param previous:    Previously selected item
+        :return:
+        """
+
+        # log.debug("on_list_selection_change()")
+        # log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
 
 
         try:
         try:
             obj = current.indexes()[0].internalPointer().obj
             obj = current.indexes()[0].internalPointer().obj
@@ -942,12 +948,12 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                 )
                 )
         except IndexError:
         except IndexError:
             self.item_selected.emit('none')
             self.item_selected.emit('none')
-            # FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
+            # log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
             self.app.inform.emit('')
             self.app.inform.emit('')
             try:
             try:
                 self.app.ui.selected_scroll_area.takeWidget()
                 self.app.ui.selected_scroll_area.takeWidget()
             except Exception as e:
             except Exception as e:
-                FlatCAMApp.App.log.debug("Nothing to remove. %s" % str(e))
+                log.debug("Nothing to remove. %s" % str(e))
 
 
             self.app.setup_component_editor()
             self.app.setup_component_editor()
             return
             return

Разница между файлами не показана из-за своего большого размера
+ 231 - 205
camlib.py


+ 36 - 34
flatcamEditors/FlatCAMExcEditor.py

@@ -12,7 +12,6 @@ from camlib import distance, arc, FlatCAMRTreeStorage
 from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
 from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
 from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
 from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
 from flatcamParsers.ParseExcellon import Excellon
 from flatcamParsers.ParseExcellon import Excellon
-import FlatCAMApp
 
 
 from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point
 from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point
 import shapely.affinity as affinity
 import shapely.affinity as affinity
@@ -179,7 +178,7 @@ class FCDrillArray(FCShapeTool):
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
             pass
 
 
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_drill_array.png'))
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_drill_array.png'))
@@ -1516,7 +1515,7 @@ class FlatCAMExcEditor(QtCore.QObject):
     draw_shape_idx = -1
     draw_shape_idx = -1
 
 
     def __init__(self, app):
     def __init__(self, app):
-        assert isinstance(app, FlatCAMApp.App), "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
+        # assert isinstance(app, FlatCAMApp.App), "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
 
 
         super(FlatCAMExcEditor, self).__init__()
         super(FlatCAMExcEditor, self).__init__()
 
 
@@ -2230,8 +2229,8 @@ class FlatCAMExcEditor(QtCore.QObject):
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
         self.editor_active = False
 
 
-        def entry2option(option, entry):
-            self.options[option] = float(entry.text())
+        # def entry2option(option, entry):
+        #     self.options[option] = float(entry.text())
 
 
         # Event signals disconnect id holders
         # Event signals disconnect id holders
         self.mp = None
         self.mp = None
@@ -2388,7 +2387,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
             try:
             try:
                 # Find no of slots for the current tool
                 # Find no of slots for the current tool
-                for slot in self.slots:
+                for slot in self.slot_points_edit:
                     if slot['tool'] == tool_no:
                     if slot['tool'] == tool_no:
                         slot_cnt += 1
                         slot_cnt += 1
 
 
@@ -2661,15 +2660,13 @@ class FlatCAMExcEditor(QtCore.QObject):
         # self.tools_table_exc.selectionModel().currentChanged.disconnect()
         # self.tools_table_exc.selectionModel().currentChanged.disconnect()
 
 
         self.is_modified = True
         self.is_modified = True
-        new_dia = None
+        # new_dia = None
 
 
-        if self.tools_table_exc.currentItem() is not None:
-            try:
-                new_dia = float(self.tools_table_exc.currentItem().text())
-            except ValueError as e:
-                log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e))
-                self.tools_table_exc.setCurrentItem(None)
-                return
+        try:
+            new_dia = float(self.tools_table_exc.currentItem().text())
+        except ValueError as e:
+            log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e))
+            return
 
 
         row_of_item_changed = self.tools_table_exc.currentRow()
         row_of_item_changed = self.tools_table_exc.currentRow()
         # rows start with 0, tools start with 1 so we adjust the value by 1
         # rows start with 0, tools start with 1 so we adjust the value by 1
@@ -3297,7 +3294,8 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
         return self.edited_obj_name
         return self.edited_obj_name
 
 
-    def update_options(self, obj):
+    @staticmethod
+    def update_options(obj):
         try:
         try:
             if not obj.options:
             if not obj.options:
                 obj.options = {}
                 obj.options = {}
@@ -3316,10 +3314,14 @@ class FlatCAMExcEditor(QtCore.QObject):
         """
         """
         Creates a new Excellon object for the edited Excellon. Thread-safe.
         Creates a new Excellon object for the edited Excellon. Thread-safe.
 
 
-        :param outname: Name of the resulting object. None causes the
-            name to be that of the file.
-        :type outname: str
-        :return: None
+        :param outname:     Name of the resulting object. None causes the
+                            name to be that of the file.
+        :type outname:      str
+
+        :param n_drills:    The new Drills storage
+        :param n_slots:     The new Slots storage
+        :param n_tools:     The new Tools storage
+        :return:            None
         """
         """
 
 
         self.app.log.debug("Update the Excellon object with edited content. Source is %s" %
         self.app.log.debug("Update the Excellon object with edited content. Source is %s" %
@@ -3429,12 +3431,12 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
             self.replot()
             self.replot()
 
 
-    def toolbar_tool_toggle(self, key):
-        self.options[key] = self.sender().isChecked()
-        if self.options[key] is True:
-            return 1
-        else:
-            return 0
+    # def toolbar_tool_toggle(self, key):
+    #     self.options[key] = self.sender().isChecked()
+    #     if self.options[key] is True:
+    #         return 1
+    #     else:
+    #         return 0
 
 
     def on_canvas_click(self, event):
     def on_canvas_click(self, event):
         """
         """
@@ -3446,12 +3448,12 @@ class FlatCAMExcEditor(QtCore.QObject):
         """
         """
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
             event_pos = event.pos
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
-            right_button = 2
+            # event_is_dragging = event.is_dragging
+            # right_button = 2
         else:
         else:
             event_pos = (event.xdata, event.ydata)
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
-            right_button = 3
+            # event_is_dragging = self.app.plotcanvas.is_dragging
+            # right_button = 3
 
 
         self.pos = self.canvas.translate_coords(event_pos)
         self.pos = self.canvas.translate_coords(event_pos)
 
 
@@ -3575,8 +3577,8 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
         if isinstance(shape, DrawToolUtilityShape):
         if isinstance(shape, DrawToolUtilityShape):
             self.utility.append(shape)
             self.utility.append(shape)
-        else:
-            self.storage.insert(shape)  # TODO: Check performance
+        # else:
+        #     self.storage.insert(shape)
 
 
     def on_exc_click_release(self, event):
     def on_exc_click_release(self, event):
         """
         """
@@ -3591,11 +3593,11 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
             event_pos = event.pos
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
+            # event_is_dragging = event.is_dragging
             right_button = 2
             right_button = 2
         else:
         else:
             event_pos = (event.xdata, event.ydata)
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
+            # event_is_dragging = self.app.plotcanvas.is_dragging
             right_button = 3
             right_button = 3
 
 
         pos_canvas = self.canvas.translate_coords(event_pos)
         pos_canvas = self.canvas.translate_coords(event_pos)
@@ -4027,7 +4029,7 @@ class FlatCAMExcEditor(QtCore.QObject):
                     del self.slot_points_edit[storage][0]
                     del self.slot_points_edit[storage][0]
 
 
         if del_shape in self.selected:
         if del_shape in self.selected:
-            self.selected.remove(del_shape)  # TODO: Check performance
+            self.selected.remove(del_shape)
 
 
     def delete_utility_geometry(self):
     def delete_utility_geometry(self):
         for_deletion = [util_shape for util_shape in self.utility]
         for_deletion = [util_shape for util_shape in self.utility]

+ 3 - 3
flatcamEditors/FlatCAMGeoEditor.py

@@ -20,7 +20,6 @@ from flatcamGUI.ObjectUI import RadioSet
 from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
 from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
     FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog, FCTree
     FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog, FCTree
 from flatcamParsers.ParseFont import *
 from flatcamParsers.ParseFont import *
-import FlatCAMApp
 
 
 from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
 from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
 from shapely.ops import cascaded_union, unary_union, linemerge
 from shapely.ops import cascaded_union, unary_union, linemerge
@@ -3299,8 +3298,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
     draw_shape_idx = -1
     draw_shape_idx = -1
 
 
     def __init__(self, app, disabled=False):
     def __init__(self, app, disabled=False):
-        assert isinstance(app, FlatCAMApp.App), \
-            "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
+        # assert isinstance(app, FlatCAMApp.App), \
+        #     "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
 
 
         super(FlatCAMGeoEditor, self).__init__()
         super(FlatCAMGeoEditor, self).__init__()
 
 
@@ -4011,6 +4010,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         :return:        Boolean. Status of the checkbox that toggled the Editor Tool
         :return:        Boolean. Status of the checkbox that toggled the Editor Tool
         """
         """
         cb_widget = self.sender()
         cb_widget = self.sender()
+        assert isinstance(cb_widget, QtWidgets.QAction), "Expected a QAction got %s" % type(cb_widget)
         self.options[key] = cb_widget.isChecked()
         self.options[key] = cb_widget.isChecked()
 
 
         return 1 if self.options[key] is True else 0
         return 1 if self.options[key] is True else 0

+ 31 - 31
flatcamEditors/FlatCAMGrbEditor.py

@@ -21,7 +21,6 @@ from camlib import distance, arc, three_point_circle
 from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
 from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
     EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
     EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
-import FlatCAMApp
 
 
 import numpy as np
 import numpy as np
 from numpy.linalg import norm as numpy_norm
 from numpy.linalg import norm as numpy_norm
@@ -182,6 +181,7 @@ class FCShapeTool(DrawTool):
 
 
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = None
 
 
     def make(self):
     def make(self):
         pass
         pass
@@ -199,7 +199,7 @@ class FCPad(FCShapeTool):
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
             pass
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_circle.png'))
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_circle.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
@@ -1415,7 +1415,7 @@ class FCDisc(FCShapeTool):
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
             pass
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_disc.png'))
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_disc.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
@@ -2422,8 +2422,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
     mp_finished = QtCore.pyqtSignal(list)
     mp_finished = QtCore.pyqtSignal(list)
 
 
     def __init__(self, app):
     def __init__(self, app):
-        assert isinstance(app, FlatCAMApp.App), \
-            "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
+        # assert isinstance(app, FlatCAMApp.App), \
+        #     "Expected the app to be a FlatCAMApp.App, got %s" % type(app)
 
 
         super(FlatCAMGrbEditor, self).__init__()
         super(FlatCAMGrbEditor, self).__init__()
 
 
@@ -3479,7 +3479,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                 current_table_dia_edited = float(self.apertures_table.currentItem().text())
                 current_table_dia_edited = float(self.apertures_table.currentItem().text())
             except ValueError as e:
             except ValueError as e:
                 log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e))
                 log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e))
-                self.apertures_table.setCurrentItem(None)
+                # self.apertures_table.setCurrentItem(None)
                 return
                 return
 
 
         row_of_item_changed = self.apertures_table.currentRow()
         row_of_item_changed = self.apertures_table.currentRow()
@@ -3956,10 +3956,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
                     global_clear_geo = []
                     global_clear_geo = []
 
 
                     # create one big geometry made out of all 'negative' (clear) polygons
                     # create one big geometry made out of all 'negative' (clear) polygons
-                    for apid in app_obj.gerber_obj.apertures:
+                    for aper_id in app_obj.gerber_obj.apertures:
                         # first check if we have any clear_geometry (LPC) and if yes added it to the global_clear_geo
                         # first check if we have any clear_geometry (LPC) and if yes added it to the global_clear_geo
-                        if 'geometry' in app_obj.gerber_obj.apertures[apid]:
-                            for elem in app_obj.gerber_obj.apertures[apid]['geometry']:
+                        if 'geometry' in app_obj.gerber_obj.apertures[aper_id]:
+                            for elem in app_obj.gerber_obj.apertures[aper_id]['geometry']:
                                 if 'clear' in elem:
                                 if 'clear' in elem:
                                     global_clear_geo.append(elem['clear'])
                                     global_clear_geo.append(elem['clear'])
                     log.warning("Found %d clear polygons." % len(global_clear_geo))
                     log.warning("Found %d clear polygons." % len(global_clear_geo))
@@ -3967,7 +3967,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                     if global_clear_geo:
                     if global_clear_geo:
                         global_clear_geo = MultiPolygon(global_clear_geo)
                         global_clear_geo = MultiPolygon(global_clear_geo)
                         if isinstance(global_clear_geo, Polygon):
                         if isinstance(global_clear_geo, Polygon):
-                            global_clear_geo = list(global_clear_geo)
+                            global_clear_geo = [global_clear_geo]
 
 
                     # we subtract the big "negative" (clear) geometry from each solid polygon but only the part of
                     # we subtract the big "negative" (clear) geometry from each solid polygon but only the part of
                     # clear geometry that fits inside the solid. otherwise we may loose the solid
                     # clear geometry that fits inside the solid. otherwise we may loose the solid
@@ -3979,8 +3979,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
                             #         solid_geo = elem['solid']
                             #         solid_geo = elem['solid']
                             #         for clear_geo in global_clear_geo:
                             #         for clear_geo in global_clear_geo:
                             #             # Make sure that the clear_geo is within the solid_geo otherwise we loose
                             #             # Make sure that the clear_geo is within the solid_geo otherwise we loose
-                            #             # the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to
-                            #             # delete it
+                            #             # the solid_geometry. We want for clear_geometry just to cut
+                            #             # into solid_geometry not to delete it
                             #             if clear_geo.within(solid_geo):
                             #             if clear_geo.within(solid_geo):
                             #                 solid_geo = solid_geo.difference(clear_geo)
                             #                 solid_geo = solid_geo.difference(clear_geo)
                             #         try:
                             #         try:
@@ -4307,14 +4307,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
 
             self.plot_all()
             self.plot_all()
 
 
-    def toolbar_tool_toggle(self, key):
-        """
-
-        :param key: key to update in self.options dictionary
-        :return:
-        """
-        self.options[key] = self.sender().isChecked()
-        return self.options[key]
+    # def toolbar_tool_toggle(self, key):
+    #     """
+    #
+    #     :param key: key to update in self.options dictionary
+    #     :return:
+    #     """
+    #     self.options[key] = self.sender().isChecked()
+    #     return self.options[key]
 
 
     def on_grb_shape_complete(self, storage=None, specific_shape=None, no_plot=False):
     def on_grb_shape_complete(self, storage=None, specific_shape=None, no_plot=False):
         """
         """
@@ -4389,12 +4389,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
         """
         """
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
             event_pos = event.pos
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
-            right_button = 2
+            # event_is_dragging = event.is_dragging
+            # right_button = 2
         else:
         else:
             event_pos = (event.xdata, event.ydata)
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
-            right_button = 3
+            # event_is_dragging = self.app.plotcanvas.is_dragging
+            # right_button = 3
 
 
         self.pos = self.canvas.translate_coords(event_pos)
         self.pos = self.canvas.translate_coords(event_pos)
 
 
@@ -4457,11 +4457,11 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.modifiers = QtWidgets.QApplication.keyboardModifiers()
         self.modifiers = QtWidgets.QApplication.keyboardModifiers()
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
             event_pos = event.pos
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
+            # event_is_dragging = event.is_dragging
             right_button = 2
             right_button = 2
         else:
         else:
             event_pos = (event.xdata, event.ydata)
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
+            # event_is_dragging = self.app.plotcanvas.is_dragging
             right_button = 3
             right_button = 3
 
 
         pos_canvas = self.canvas.translate_coords(event_pos)
         pos_canvas = self.canvas.translate_coords(event_pos)
@@ -4747,10 +4747,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
         Plots a geometric object or list of objects without rendering. Plotted objects
         Plots a geometric object or list of objects without rendering. Plotted objects
         are returned as a list. This allows for efficient/animated rendering.
         are returned as a list. This allows for efficient/animated rendering.
 
 
-        :param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such)
-        :param color: Shape color
-        :param linewidth: Width of lines in # of pixels.
-        :return: List of plotted elements.
+        :param geometry:    Geometry to be plotted (Any Shapely.geom kind or list of such)
+        :param color:       Shape color
+        :param linewidth:   Width of lines in # of pixels.
+        :return:            List of plotted elements.
         """
         """
 
 
         if geometry is None:
         if geometry is None:
@@ -5597,7 +5597,7 @@ class TransformEditorTool(FlatCAMTool):
             self.flip_ref_entry.set_value((0, 0))
             self.flip_ref_entry.set_value((0, 0))
 
 
     def template(self):
     def template(self):
-        if not self.fcdraw.selected:
+        if not self.draw_app.selected:
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
             return
             return
 
 

+ 2 - 2
flatcamGUI/FlatCAMGUI.py

@@ -2876,7 +2876,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
                 # Open Excellon file
                 # Open Excellon file
                 if key == QtCore.Qt.Key_E:
                 if key == QtCore.Qt.Key_E:
-                    self.app.on_fileopenexcellon()
+                    self.app.on_fileopenexcellon(signal=None)
 
 
                 # Open Gerber file
                 # Open Gerber file
                 if key == QtCore.Qt.Key_G:
                 if key == QtCore.Qt.Key_G:
@@ -2884,7 +2884,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                     if 'editor' in widget_name.lower():
                     if 'editor' in widget_name.lower():
                         self.app.goto_text_line()
                         self.app.goto_text_line()
                     else:
                     else:
-                        self.app.on_fileopengerber()
+                        self.app.on_fileopengerber(signal=None)
 
 
                 # Distance Tool
                 # Distance Tool
                 if key == QtCore.Qt.Key_M:
                 if key == QtCore.Qt.Key_M:

+ 6 - 8
flatcamGUI/PlotCanvasLegacy.py

@@ -16,8 +16,6 @@ from descartes.patch import PolygonPatch
 
 
 from shapely.geometry import Polygon, LineString, LinearRing
 from shapely.geometry import Polygon, LineString, LinearRing
 
 
-import FlatCAMApp
-
 from copy import deepcopy
 from copy import deepcopy
 import logging
 import logging
 
 
@@ -496,7 +494,7 @@ class PlotCanvasLegacy(QtCore.QObject):
         :param event:
         :param event:
         :return:
         :return:
         """
         """
-        FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
+        log.debug('on_key_down(): ' + str(event.key))
         self.key = event.key
         self.key = event.key
 
 
     def on_key_up(self, event):
     def on_key_up(self, event):
@@ -531,7 +529,7 @@ class PlotCanvasLegacy(QtCore.QObject):
         try:
         try:
             self.figure.clf()
             self.figure.clf()
         except KeyError:
         except KeyError:
-            FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")
+            log.warning("KeyError in MPL figure.clf()")
 
 
         # Re-build
         # Re-build
         self.figure.add_axes(self.axes)
         self.figure.add_axes(self.axes)
@@ -582,7 +580,7 @@ class PlotCanvasLegacy(QtCore.QObject):
         try:
         try:
             r = width / height
             r = width / height
         except ZeroDivisionError:
         except ZeroDivisionError:
-            FlatCAMApp.App.log.error("Height is %f" % height)
+            log.error("Height is %f" % height)
             return
             return
         canvas_w, canvas_h = self.canvas.get_width_height()
         canvas_w, canvas_h = self.canvas.get_width_height()
         canvas_r = float(canvas_w) / canvas_h
         canvas_r = float(canvas_w) / canvas_h
@@ -1190,10 +1188,10 @@ class ShapeCollectionLegacy:
                                                  linewidth=local_shapes[element]['linewidth'])
                                                  linewidth=local_shapes[element]['linewidth'])
                             self.axes.add_patch(patch)
                             self.axes.add_patch(patch)
                         except AssertionError:
                         except AssertionError:
-                            FlatCAMApp.App.log.warning("A geometry component was not a polygon:")
-                            FlatCAMApp.App.log.warning(str(element))
+                            log.warning("A geometry component was not a polygon:")
+                            log.warning(str(element))
                         except Exception as e:
                         except Exception as e:
-                            FlatCAMApp.App.log.debug(
+                            log.debug(
                                 "PlotCanvasLegacy.ShepeCollectionLegacy.redraw() gerber 'solid' --> %s" % str(e))
                                 "PlotCanvasLegacy.ShepeCollectionLegacy.redraw() gerber 'solid' --> %s" % str(e))
                     else:
                     else:
                         try:
                         try:

+ 62 - 63
flatcamParsers/ParseExcellon.py

@@ -7,7 +7,6 @@
 # ########################################################## ##
 # ########################################################## ##
 
 
 from camlib import Geometry
 from camlib import Geometry
-import FlatCAMApp
 
 
 import shapely.affinity as affinity
 import shapely.affinity as affinity
 from shapely.geometry import Point, LineString
 from shapely.geometry import Point, LineString
@@ -19,6 +18,7 @@ import traceback
 from copy import deepcopy
 from copy import deepcopy
 
 
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
+from FlatCAMCommon import GracefulException as grace
 
 
 import gettext
 import gettext
 import builtins
 import builtins
@@ -86,6 +86,7 @@ class Excellon(Geometry):
         :return: Excellon object.
         :return: Excellon object.
         :rtype: Excellon
         :rtype: Excellon
         """
         """
+
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
         if geo_steps_per_circle is None:
         if geo_steps_per_circle is None:
@@ -241,12 +242,12 @@ class Excellon(Geometry):
 
 
     def parse_file(self, filename=None, file_obj=None):
     def parse_file(self, filename=None, file_obj=None):
         """
         """
-        Reads the specified file as array of lines as
-        passes it to ``parse_lines()``.
+        Reads the specified file as array of lines as passes it to ``parse_lines()``.
 
 
-        :param filename: The file to be read and parsed.
-        :type filename: str
-        :return: None
+        :param filename:    The file to be read and parsed.
+        :param file_obj:
+        :type filename:     str
+        :return:            None
         """
         """
         if file_obj:
         if file_obj:
             estr = file_obj
             estr = file_obj
@@ -298,7 +299,7 @@ class Excellon(Geometry):
             for eline in elines:
             for eline in elines:
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
 
                 line_num += 1
                 line_num += 1
                 # log.debug("%3d %s" % (line_num, str(eline)))
                 # log.debug("%3d %s" % (line_num, str(eline)))
@@ -526,7 +527,7 @@ class Excellon(Geometry):
                             slot_dia = 0.05
                             slot_dia = 0.05
                             try:
                             try:
                                 slot_dia = float(self.tools[current_tool]['C'])
                                 slot_dia = float(self.tools[current_tool]['C'])
-                            except Exception as e:
+                            except Exception:
                                 pass
                                 pass
                             log.debug(
                             log.debug(
                                 'Milling/Drilling slot with tool %s, diam=%f' % (
                                 'Milling/Drilling slot with tool %s, diam=%f' % (
@@ -596,7 +597,7 @@ class Excellon(Geometry):
                             slot_dia = 0.05
                             slot_dia = 0.05
                             try:
                             try:
                                 slot_dia = float(self.tools[current_tool]['C'])
                                 slot_dia = float(self.tools[current_tool]['C'])
-                            except Exception as e:
+                            except Exception:
                                 pass
                                 pass
                             log.debug(
                             log.debug(
                                 'Milling/Drilling slot with tool %s, diam=%f' % (
                                 'Milling/Drilling slot with tool %s, diam=%f' % (
@@ -893,9 +894,8 @@ class Excellon(Geometry):
             log.info("Zeros: %s, Units %s." % (self.zeros, self.units))
             log.info("Zeros: %s, Units %s." % (self.zeros, self.units))
         except Exception:
         except Exception:
             log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline))
             log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline))
-            msg = '[ERROR_NOTCL] %s' % \
-                  _("An internal error has ocurred. See shell.\n")
-            msg += ('{e_code} {tx} {l_nr}: {line}\n').format(
+            msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n")
+            msg += '{e_code} {tx} {l_nr}: {line}\n'.format(
                 e_code='[ERROR]',
                 e_code='[ERROR]',
                 tx=_("Excellon Parser error.\nParsing Failed. Line"),
                 tx=_("Excellon Parser error.\nParsing Failed. Line"),
                 l_nr=line_num,
                 l_nr=line_num,
@@ -1010,13 +1010,13 @@ class Excellon(Geometry):
                       "Excellon geometry creation failed due of ERROR: %s" % str(e))
                       "Excellon geometry creation failed due of ERROR: %s" % str(e))
             return "fail"
             return "fail"
 
 
-    def bounds(self):
+    def bounds(self, flatten=None):
         """
         """
         Returns coordinates of rectangular bounds
         Returns coordinates of rectangular bounds
         of Excellon geometry: (xmin, ymin, xmax, ymax).
         of Excellon geometry: (xmin, ymin, xmax, ymax).
+
+        :param flatten:     No used
         """
         """
-        # fixed issue of getting bounds only for one level lists of objects
-        # now it can get bounds for nested lists of objects
 
 
         log.debug("flatcamParsers.ParseExcellon.Excellon.bounds()")
         log.debug("flatcamParsers.ParseExcellon.Excellon.bounds()")
 
 
@@ -1056,11 +1056,11 @@ class Excellon(Geometry):
         maxy_list = []
         maxy_list = []
 
 
         for tool in self.tools:
         for tool in self.tools:
-            minx, miny, maxx, maxy = bounds_rec(self.tools[tool]['solid_geometry'])
-            minx_list.append(minx)
-            miny_list.append(miny)
-            maxx_list.append(maxx)
-            maxy_list.append(maxy)
+            eminx, eminy, emaxx, emaxy = bounds_rec(self.tools[tool]['solid_geometry'])
+            minx_list.append(eminx)
+            miny_list.append(eminy)
+            maxx_list.append(emaxx)
+            maxy_list.append(emaxy)
 
 
         return min(minx_list), min(miny_list), max(maxx_list), max(maxy_list)
         return min(minx_list), min(miny_list), max(maxx_list), max(maxy_list)
 
 
@@ -1075,8 +1075,9 @@ class Excellon(Geometry):
 
 
         Kind of convolute way to make the conversion and it is based on the assumption that the Excellon file
         Kind of convolute way to make the conversion and it is based on the assumption that the Excellon file
         will have detected the units before the tools are parsed and stored in self.tools
         will have detected the units before the tools are parsed and stored in self.tools
-        :param units:
-        :type str: IN or MM
+
+        :param units:   'IN' or 'MM'. String
+
         :return:
         :return:
         """
         """
 
 
@@ -1109,12 +1110,13 @@ class Excellon(Geometry):
         Scales geometry on the XY plane in the object by a given factor.
         Scales geometry on the XY plane in the object by a given factor.
         Tool sizes, feedrates an Z-plane dimensions are untouched.
         Tool sizes, feedrates an Z-plane dimensions are untouched.
 
 
-        :param xfactor: Number by which to scale the object.
-        :type xfactor: float
-        :param yfactor: Number by which to scale the object.
-        :type yfactor: float
-        :return: None
-        :rtype: NOne
+        :param xfactor:     Number by which to scale the object.
+        :type xfactor:      float
+        :param yfactor:     Number by which to scale the object.
+        :type yfactor:      float
+        :param point:       Origin point for scale
+        :return:            None
+        :rtype:             None
         """
         """
         log.debug("flatcamParsers.ParseExcellon.Excellon.scale()")
         log.debug("flatcamParsers.ParseExcellon.Excellon.scale()")
 
 
@@ -1145,8 +1147,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         # variables to display the percentage of work done
         self.geo_len = 0
         self.geo_len = 0
         try:
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
         except TypeError:
             self.geo_len = 1
             self.geo_len = 1
         self.old_disp_number = 0
         self.old_disp_number = 0
@@ -1190,12 +1191,12 @@ class Excellon(Geometry):
             return
             return
 
 
         def offset_geom(obj):
         def offset_geom(obj):
-            if type(obj) is list:
+            try:
                 new_obj = []
                 new_obj = []
-                for g in obj:
-                    new_obj.append(offset_geom(g))
+                for geo in obj:
+                    new_obj.append(offset_geom(geo))
                 return new_obj
                 return new_obj
-            else:
+            except TypeError:
                 try:
                 try:
                     return affinity.translate(obj, xoff=dx, yoff=dy)
                     return affinity.translate(obj, xoff=dx, yoff=dy)
                 except AttributeError:
                 except AttributeError:
@@ -1204,8 +1205,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         # variables to display the percentage of work done
         self.geo_len = 0
         self.geo_len = 0
         try:
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
         except TypeError:
             self.geo_len = 1
             self.geo_len = 1
         self.old_disp_number = 0
         self.old_disp_number = 0
@@ -1237,11 +1237,11 @@ class Excellon(Geometry):
     def mirror(self, axis, point):
     def mirror(self, axis, point):
         """
         """
 
 
-        :param axis: "X" or "Y" indicates around which axis to mirror.
-        :type axis: str
-        :param point: [x, y] point belonging to the mirror axis.
-        :type point: list
-        :return: None
+        :param axis:        "X" or "Y" indicates around which axis to mirror.
+        :type axis:         str
+        :param point:       [x, y] point belonging to the mirror axis.
+        :type point:        list
+        :return:            None
         """
         """
         log.debug("flatcamParsers.ParseExcellon.Excellon.mirror()")
         log.debug("flatcamParsers.ParseExcellon.Excellon.mirror()")
 
 
@@ -1249,12 +1249,12 @@ class Excellon(Geometry):
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
 
 
         def mirror_geom(obj):
         def mirror_geom(obj):
-            if type(obj) is list:
+            try:
                 new_obj = []
                 new_obj = []
-                for g in obj:
-                    new_obj.append(mirror_geom(g))
+                for geo in obj:
+                    new_obj.append(mirror_geom(geo))
                 return new_obj
                 return new_obj
-            else:
+            except TypeError:
                 try:
                 try:
                     return affinity.scale(obj, xscale, yscale, origin=(px, py))
                     return affinity.scale(obj, xscale, yscale, origin=(px, py))
                 except AttributeError:
                 except AttributeError:
@@ -1265,8 +1265,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         # variables to display the percentage of work done
         self.geo_len = 0
         self.geo_len = 0
         try:
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
         except TypeError:
             self.geo_len = 1
             self.geo_len = 1
         self.old_disp_number = 0
         self.old_disp_number = 0
@@ -1300,12 +1299,12 @@ class Excellon(Geometry):
         Shear/Skew the geometries of an object by angles along x and y dimensions.
         Shear/Skew the geometries of an object by angles along x and y dimensions.
         Tool sizes, feedrates an Z-plane dimensions are untouched.
         Tool sizes, feedrates an Z-plane dimensions are untouched.
 
 
-        Parameters
-        ----------
-        xs, ys : float, float
+        :param angle_x:
+        :param angle_y:
             The shear angle(s) for the x and y axes respectively. These can be
             The shear angle(s) for the x and y axes respectively. These can be
             specified in either degrees (default) or radians by setting
             specified in either degrees (default) or radians by setting
             use_radians=True.
             use_radians=True.
+        :param point:       Origin point for Skew
 
 
         See shapely manual for more information:
         See shapely manual for more information:
         http://toblerity.org/shapely/manual.html#affine-transformations
         http://toblerity.org/shapely/manual.html#affine-transformations
@@ -1322,12 +1321,12 @@ class Excellon(Geometry):
             return
             return
 
 
         def skew_geom(obj):
         def skew_geom(obj):
-            if type(obj) is list:
+            try:
                 new_obj = []
                 new_obj = []
                 for g in obj:
                 for g in obj:
                     new_obj.append(skew_geom(g))
                     new_obj.append(skew_geom(g))
                 return new_obj
                 return new_obj
-            else:
+            except TypeError:
                 try:
                 try:
                     return affinity.skew(obj, angle_x, angle_y, origin=(px, py))
                     return affinity.skew(obj, angle_x, angle_y, origin=(px, py))
                 except AttributeError:
                 except AttributeError:
@@ -1336,8 +1335,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         # variables to display the percentage of work done
         self.geo_len = 0
         self.geo_len = 0
         try:
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
         except TypeError:
             self.geo_len = 1
             self.geo_len = 1
         self.old_disp_number = 0
         self.old_disp_number = 0
@@ -1393,9 +1391,10 @@ class Excellon(Geometry):
     def rotate(self, angle, point=None):
     def rotate(self, angle, point=None):
         """
         """
         Rotate the geometry of an object by an angle around the 'point' coordinates
         Rotate the geometry of an object by an angle around the 'point' coordinates
+
         :param angle:
         :param angle:
-        :param point: tuple of coordinates (x, y)
-        :return:
+        :param point:   tuple of coordinates (x, y)
+        :return:        None
         """
         """
         log.debug("flatcamParsers.ParseExcellon.Excellon.rotate()")
         log.debug("flatcamParsers.ParseExcellon.Excellon.rotate()")
 
 
@@ -1423,8 +1422,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         # variables to display the percentage of work done
         self.geo_len = 0
         self.geo_len = 0
         try:
         try:
-            for g in self.drills:
-                self.geo_len += 1
+            self.geo_len = len(self.drills)
         except TypeError:
         except TypeError:
             self.geo_len = 1
             self.geo_len = 1
         self.old_disp_number = 0
         self.old_disp_number = 0
@@ -1476,9 +1474,10 @@ class Excellon(Geometry):
     def buffer(self, distance, join, factor):
     def buffer(self, distance, join, factor):
         """
         """
 
 
-        :param distance: if 'factor' is True then distance is the factor
-        :param factor: True or False (None)
-        :return:
+        :param distance:    if 'factor' is True then distance is the factor
+        :param factor:      True or False (None)
+        :param join:        The type of line joint used by the shapely buffer method: round, square, bevel
+        :return:            None
         """
         """
         log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()")
         log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()")
 
 
@@ -1486,12 +1485,12 @@ class Excellon(Geometry):
             return
             return
 
 
         def buffer_geom(obj):
         def buffer_geom(obj):
-            if type(obj) is list:
+            try:
                 new_obj = []
                 new_obj = []
                 for g in obj:
                 for g in obj:
                     new_obj.append(buffer_geom(g))
                     new_obj.append(buffer_geom(g))
                 return new_obj
                 return new_obj
-            else:
+            except TypeError:
                 try:
                 try:
                     if factor is None:
                     if factor is None:
                         return obj.buffer(distance, resolution=self.geo_steps_per_circle)
                         return obj.buffer(distance, resolution=self.geo_steps_per_circle)

+ 24 - 25
flatcamParsers/ParseGerber.py

@@ -1,6 +1,5 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from camlib import Geometry, arc, arc_angle, ApertureMacro
 from camlib import Geometry, arc, arc_angle, ApertureMacro
-import FlatCAMApp
 
 
 import numpy as np
 import numpy as np
 import re
 import re
@@ -9,15 +8,16 @@ import traceback
 from copy import deepcopy
 from copy import deepcopy
 import sys
 import sys
 
 
-from shapely.ops import cascaded_union, unary_union
-from shapely.geometry import Polygon, MultiPolygon, LineString, Point
+from shapely.ops import cascaded_union
+from shapely.affinity import scale, translate
 import shapely.affinity as affinity
 import shapely.affinity as affinity
-from shapely.geometry import box as shply_box
+from shapely.geometry import box as shply_box, Polygon, LineString, Point, MultiPolygon
 
 
 from lxml import etree as ET
 from lxml import etree as ET
-from flatcamParsers.ParseSVG import *
-
+from flatcamParsers.ParseSVG import svgparselength, getsvggeo
+from FlatCAMCommon import GracefulException as grace
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
+
 import gettext
 import gettext
 import builtins
 import builtins
 
 
@@ -255,7 +255,7 @@ class Gerber(Geometry):
         """
         """
         if self.app.abort_flag:
         if self.app.abort_flag:
             # graceful abort requested by the user
             # graceful abort requested by the user
-            raise FlatCAMApp.GracefulException
+            raise grace
 
 
         # Found some Gerber with a leading zero in the aperture id and the
         # Found some Gerber with a leading zero in the aperture id and the
         # referenced it without the zero, so this is a hack to handle that.
         # referenced it without the zero, so this is a hack to handle that.
@@ -403,7 +403,7 @@ class Gerber(Geometry):
 
 
         # Absolute or Relative/Incremental coordinates
         # Absolute or Relative/Incremental coordinates
         # Not implemented
         # Not implemented
-        absolute = True
+        # absolute = True
 
 
         # How to interpret circular interpolation: SINGLE or MULTI
         # How to interpret circular interpolation: SINGLE or MULTI
         quadrant_mode = None
         quadrant_mode = None
@@ -428,7 +428,7 @@ class Gerber(Geometry):
             for gline in glines:
             for gline in glines:
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
 
                 line_num += 1
                 line_num += 1
                 self.source_file += gline + '\n'
                 self.source_file += gline + '\n'
@@ -986,7 +986,7 @@ class Gerber(Geometry):
                                         if 'geometry' not in self.apertures[current_aperture]:
                                         if 'geometry' not in self.apertures[current_aperture]:
                                             self.apertures[current_aperture]['geometry'] = []
                                             self.apertures[current_aperture]['geometry'] = []
                                         self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
                                         self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
-                                except Exception as e:
+                                except Exception:
                                     pass
                                     pass
                             last_path_aperture = current_aperture
                             last_path_aperture = current_aperture
                             # we do this for the case that a region is done without having defined any aperture
                             # we do this for the case that a region is done without having defined any aperture
@@ -1229,25 +1229,25 @@ class Gerber(Geometry):
                     try:
                     try:
                         circular_x = parse_gerber_number(circular_x,
                         circular_x = parse_gerber_number(circular_x,
                                                          self.int_digits, self.frac_digits, self.gerber_zeros)
                                                          self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
+                    except Exception:
                         circular_x = current_x
                         circular_x = current_x
 
 
                     try:
                     try:
                         circular_y = parse_gerber_number(circular_y,
                         circular_y = parse_gerber_number(circular_y,
                                                          self.int_digits, self.frac_digits, self.gerber_zeros)
                                                          self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
+                    except Exception:
                         circular_y = current_y
                         circular_y = current_y
 
 
                     # According to Gerber specification i and j are not modal, which means that when i or j are missing,
                     # According to Gerber specification i and j are not modal, which means that when i or j are missing,
                     # they are to be interpreted as being zero
                     # they are to be interpreted as being zero
                     try:
                     try:
                         i = parse_gerber_number(i, self.int_digits, self.frac_digits, self.gerber_zeros)
                         i = parse_gerber_number(i, self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
+                    except Exception:
                         i = 0
                         i = 0
 
 
                     try:
                     try:
                         j = parse_gerber_number(j, self.int_digits, self.frac_digits, self.gerber_zeros)
                         j = parse_gerber_number(j, self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
+                    except Exception:
                         j = 0
                         j = 0
 
 
                     if quadrant_mode is None:
                     if quadrant_mode is None:
@@ -1668,13 +1668,14 @@ class Gerber(Geometry):
             bbox = bbox.envelope
             bbox = bbox.envelope
         return bbox
         return bbox
 
 
-    def bounds(self):
+    def bounds(self, flatten=None):
         """
         """
         Returns coordinates of rectangular bounds
         Returns coordinates of rectangular bounds
         of Gerber geometry: (xmin, ymin, xmax, ymax).
         of Gerber geometry: (xmin, ymin, xmax, ymax).
+
+        :param flatten:     Not used, it is here for compatibility with base class method
+        :return:            None
         """
         """
-        # fixed issue of getting bounds only for one level lists of objects
-        # now it can get bounds for nested lists of objects
 
 
         log.debug("parseGerber.Gerber.bounds()")
         log.debug("parseGerber.Gerber.bounds()")
 
 
@@ -1999,8 +2000,7 @@ class Gerber(Geometry):
         # variables to display the percentage of work done
         # variables to display the percentage of work done
         self.geo_len = 0
         self.geo_len = 0
         try:
         try:
-            for __ in self.solid_geometry:
-                self.geo_len += 1
+            self.geo_len = len(self.solid_geometry)
         except TypeError:
         except TypeError:
             self.geo_len = 1
             self.geo_len = 1
 
 
@@ -2078,8 +2078,7 @@ class Gerber(Geometry):
         # variables to display the percentage of work done
         # variables to display the percentage of work done
         self.geo_len = 0
         self.geo_len = 0
         try:
         try:
-            for __ in self.solid_geometry:
-                self.geo_len += 1
+            self.geo_len = len(self.solid_geometry)
         except TypeError:
         except TypeError:
             self.geo_len = 1
             self.geo_len = 1
 
 
@@ -2217,8 +2216,7 @@ class Gerber(Geometry):
         # variables to display the percentage of work done
         # variables to display the percentage of work done
         self.geo_len = 0
         self.geo_len = 0
         try:
         try:
-            for __ in self.solid_geometry:
-                self.geo_len += 1
+            self.geo_len = len(self.solid_geometry)
         except TypeError:
         except TypeError:
             self.geo_len = 1
             self.geo_len = 1
 
 
@@ -2266,8 +2264,9 @@ class Gerber(Geometry):
     def buffer(self, distance, join, factor=None):
     def buffer(self, distance, join, factor=None):
         """
         """
 
 
-        :param distance: if 'factor' is True then distance is the factor
-        :param factor: True or False (None)
+        :param distance:    If 'factor' is True then distance is the factor
+        :param join:        The type of joining used by the Shapely buffer method. Can be: round, square and bevel
+        :param factor:      True or False (None)
         :return:
         :return:
         """
         """
         log.debug("parseGerber.Gerber.buffer()")
         log.debug("parseGerber.Gerber.buffer()")

+ 3 - 3
flatcamParsers/ParseHPGL2.py

@@ -7,7 +7,6 @@
 # ############################################################
 # ############################################################
 
 
 from camlib import arc, three_point_circle
 from camlib import arc, three_point_circle
-import FlatCAMApp
 
 
 import numpy as np
 import numpy as np
 import re
 import re
@@ -19,6 +18,7 @@ import sys
 from shapely.ops import unary_union
 from shapely.ops import unary_union
 from shapely.geometry import LineString, Point
 from shapely.geometry import LineString, Point
 
 
+from FlatCAMCommon import GracefulException as grace
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
 import gettext
 import gettext
 import builtins
 import builtins
@@ -180,7 +180,7 @@ class HPGL2:
             for gline in glines:
             for gline in glines:
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
 
                 line_num += 1
                 line_num += 1
                 self.source_file += gline + '\n'
                 self.source_file += gline + '\n'
@@ -304,7 +304,7 @@ class HPGL2:
                                                  (_("Coordinates missing, line ignored"), str(gline)))
                                                  (_("Coordinates missing, line ignored"), str(gline)))
 
 
                         if current_x is not None and current_y is not None:
                         if current_x is not None and current_y is not None:
-                            radius = match.group(1)
+                            radius = float(match.group(1))
                             geo = Point((current_x, current_y)).buffer(radius, int(self.steps_per_circle))
                             geo = Point((current_x, current_y)).buffer(radius, int(self.steps_per_circle))
                             geo_line = geo.exterior
                             geo_line = geo.exterior
                             self.tools[current_tool]['solid_geometry'].append(geo_line)
                             self.tools[current_tool]['solid_geometry'].append(geo_line)

+ 7 - 8
flatcamTools/ToolCopperThieving.py

@@ -7,10 +7,9 @@
 
 
 from PyQt5 import QtWidgets, QtCore
 from PyQt5 import QtWidgets, QtCore
 
 
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet, FCEntry, FCComboBox
 from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet, FCEntry, FCComboBox
-from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon
 
 
 import shapely.geometry.base as base
 import shapely.geometry.base as base
 from shapely.ops import cascaded_union, unary_union
 from shapely.ops import cascaded_union, unary_union
@@ -994,7 +993,7 @@ class ToolCopperThieving(FlatCAMTool):
                 for pol in app_obj.grb_object.solid_geometry:
                 for pol in app_obj.grb_object.solid_geometry:
                     if app_obj.app.abort_flag:
                     if app_obj.app.abort_flag:
                         # graceful abort requested by the user
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
 
 
                     clearance_geometry.append(
                     clearance_geometry.append(
                         pol.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4))
                         pol.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4))
@@ -1073,7 +1072,7 @@ class ToolCopperThieving(FlatCAMTool):
                     for poly in working_obj:
                     for poly in working_obj:
                         if app_obj.app.abort_flag:
                         if app_obj.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
                         geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
                         geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
                 except TypeError:
                 except TypeError:
                     geo_buff_list.append(working_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
                     geo_buff_list.append(working_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
@@ -1082,7 +1081,7 @@ class ToolCopperThieving(FlatCAMTool):
             else:   # ref_selected == 'box'
             else:   # ref_selected == 'box'
                 geo_n = working_obj.solid_geometry
                 geo_n = working_obj.solid_geometry
 
 
-                if isinstance(working_obj, FlatCAMGeometry):
+                if working_obj.kind == 'geometry':
                     try:
                     try:
                         __ = iter(geo_n)
                         __ = iter(geo_n)
                     except Exception as e:
                     except Exception as e:
@@ -1093,11 +1092,11 @@ class ToolCopperThieving(FlatCAMTool):
                     for poly in geo_n:
                     for poly in geo_n:
                         if app_obj.app.abort_flag:
                         if app_obj.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
                         geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
                         geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
 
 
                     bounding_box = cascaded_union(geo_buff_list)
                     bounding_box = cascaded_union(geo_buff_list)
-                elif isinstance(working_obj, FlatCAMGerber):
+                elif working_obj.kind == 'gerber':
                     geo_n = cascaded_union(geo_n).convex_hull
                     geo_n = cascaded_union(geo_n).convex_hull
                     bounding_box = cascaded_union(thieving_obj.solid_geometry).convex_hull.intersection(geo_n)
                     bounding_box = cascaded_union(thieving_obj.solid_geometry).convex_hull.intersection(geo_n)
                     bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
                     bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
@@ -1192,7 +1191,7 @@ class ToolCopperThieving(FlatCAMTool):
                     for pol in app_obj.grb_object.solid_geometry:
                     for pol in app_obj.grb_object.solid_geometry:
                         if app_obj.app.abort_flag:
                         if app_obj.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
 
                         outline_geometry.append(
                         outline_geometry.append(
                             pol.buffer(c_val+half_thick_line, int(int(app_obj.geo_steps_per_circle) / 4))
                             pol.buffer(c_val+half_thick_line, int(int(app_obj.geo_steps_per_circle) / 4))

+ 44 - 39
flatcamTools/ToolCutOut.py

@@ -8,7 +8,6 @@
 from PyQt5 import QtWidgets, QtGui, QtCore
 from PyQt5 import QtWidgets, QtGui, QtCore
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox, OptionalInputSection, FCButton
 from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox, OptionalInputSection, FCButton
-from FlatCAMObj import FlatCAMGerber
 
 
 from shapely.geometry import box, MultiPolygon, Polygon, LineString, LinearRing
 from shapely.geometry import box, MultiPolygon, Polygon, LineString, LinearRing
 from shapely.ops import cascaded_union, unary_union
 from shapely.ops import cascaded_union, unary_union
@@ -270,7 +269,7 @@ class CutOut(FlatCAMTool):
         form_layout_2.addRow(gaps_label, self.gaps)
         form_layout_2.addRow(gaps_label, self.gaps)
 
 
         # Buttons
         # Buttons
-        self.ff_cutout_object_btn = QtWidgets.QPushButton(_("Generate Freeform Geometry"))
+        self.ff_cutout_object_btn = FCButton(_("Generate Freeform Geometry"))
         self.ff_cutout_object_btn.setToolTip(
         self.ff_cutout_object_btn.setToolTip(
             _("Cutout the selected object.\n"
             _("Cutout the selected object.\n"
               "The cutout shape can be of any shape.\n"
               "The cutout shape can be of any shape.\n"
@@ -284,7 +283,7 @@ class CutOut(FlatCAMTool):
                         """)
                         """)
         grid0.addWidget(self.ff_cutout_object_btn, 20, 0, 1, 2)
         grid0.addWidget(self.ff_cutout_object_btn, 20, 0, 1, 2)
 
 
-        self.rect_cutout_object_btn = QtWidgets.QPushButton(_("Generate Rectangular Geometry"))
+        self.rect_cutout_object_btn = FCButton(_("Generate Rectangular Geometry"))
         self.rect_cutout_object_btn.setToolTip(
         self.rect_cutout_object_btn.setToolTip(
             _("Cutout the selected object.\n"
             _("Cutout the selected object.\n"
               "The resulting cutout shape is\n"
               "The resulting cutout shape is\n"
@@ -335,7 +334,7 @@ class CutOut(FlatCAMTool):
 
 
         # form_layout_3.addRow(e_lab_0)
         # form_layout_3.addRow(e_lab_0)
 
 
-        self.man_geo_creation_btn = QtWidgets.QPushButton(_("Generate Manual Geometry"))
+        self.man_geo_creation_btn = FCButton(_("Generate Manual Geometry"))
         self.man_geo_creation_btn.setToolTip(
         self.man_geo_creation_btn.setToolTip(
             _("If the object to be cutout is a Gerber\n"
             _("If the object to be cutout is a Gerber\n"
               "first create a Geometry that surrounds it,\n"
               "first create a Geometry that surrounds it,\n"
@@ -350,7 +349,7 @@ class CutOut(FlatCAMTool):
                         """)
                         """)
         grid0.addWidget(self.man_geo_creation_btn, 24, 0, 1, 2)
         grid0.addWidget(self.man_geo_creation_btn, 24, 0, 1, 2)
 
 
-        self.man_gaps_creation_btn = QtWidgets.QPushButton(_("Manual Add Bridge Gaps"))
+        self.man_gaps_creation_btn = FCButton(_("Manual Add Bridge Gaps"))
         self.man_gaps_creation_btn.setToolTip(
         self.man_gaps_creation_btn.setToolTip(
             _("Use the left mouse button (LMB) click\n"
             _("Use the left mouse button (LMB) click\n"
               "to create a bridge gap to separate the PCB from\n"
               "to create a bridge gap to separate the PCB from\n"
@@ -369,7 +368,7 @@ class CutOut(FlatCAMTool):
         self.layout.addStretch()
         self.layout.addStretch()
 
 
         # ## Reset Tool
         # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button = FCButton(_("Reset Tool"))
         self.reset_button.setToolTip(
         self.reset_button.setToolTip(
             _("Will reset the tool parameters.")
             _("Will reset the tool parameters.")
         )
         )
@@ -525,7 +524,7 @@ class CutOut(FlatCAMTool):
         def geo_init(geo_obj, app_obj):
         def geo_init(geo_obj, app_obj):
             solid_geo = []
             solid_geo = []
 
 
-            if isinstance(cutout_obj, FlatCAMGerber):
+            if cutout_obj.kind == 'gerber':
                 if isinstance(cutout_obj.solid_geometry, list):
                 if isinstance(cutout_obj.solid_geometry, list):
                     cutout_obj.solid_geometry = MultiPolygon(cutout_obj.solid_geometry)
                     cutout_obj.solid_geometry = MultiPolygon(cutout_obj.solid_geometry)
 
 
@@ -542,12 +541,12 @@ class CutOut(FlatCAMTool):
 
 
             def cutout_handler(geom):
             def cutout_handler(geom):
                 # Get min and max data for each object as we just cut rectangles across X or Y
                 # Get min and max data for each object as we just cut rectangles across X or Y
-                xmin, ymin, xmax, ymax = recursive_bounds(geom)
+                xxmin, yymin, xxmax, yymax = recursive_bounds(geom)
 
 
-                px = 0.5 * (xmin + xmax) + margin
-                py = 0.5 * (ymin + ymax) + margin
-                lenx = (xmax - xmin) + (margin * 2)
-                leny = (ymax - ymin) + (margin * 2)
+                px = 0.5 * (xxmin + xxmax) + margin
+                py = 0.5 * (yymin + yymax) + margin
+                lenx = (xxmax - xxmin) + (margin * 2)
+                leny = (yymax - yymin) + (margin * 2)
 
 
                 proc_geometry = []
                 proc_geometry = []
                 if gaps == 'None':
                 if gaps == 'None':
@@ -555,41 +554,41 @@ class CutOut(FlatCAMTool):
                 else:
                 else:
                     if gaps == '8' or gaps == '2LR':
                     if gaps == '8' or gaps == '2LR':
                         geom = self.subtract_poly_from_geo(geom,
                         geom = self.subtract_poly_from_geo(geom,
-                                                           xmin - gapsize,  # botleft_x
+                                                           xxmin - gapsize,  # botleft_x
                                                            py - gapsize + leny / 4,  # botleft_y
                                                            py - gapsize + leny / 4,  # botleft_y
-                                                           xmax + gapsize,  # topright_x
+                                                           xxmax + gapsize,  # topright_x
                                                            py + gapsize + leny / 4)  # topright_y
                                                            py + gapsize + leny / 4)  # topright_y
                         geom = self.subtract_poly_from_geo(geom,
                         geom = self.subtract_poly_from_geo(geom,
-                                                           xmin - gapsize,
+                                                           xxmin - gapsize,
                                                            py - gapsize - leny / 4,
                                                            py - gapsize - leny / 4,
-                                                           xmax + gapsize,
+                                                           xxmax + gapsize,
                                                            py + gapsize - leny / 4)
                                                            py + gapsize - leny / 4)
 
 
                     if gaps == '8' or gaps == '2TB':
                     if gaps == '8' or gaps == '2TB':
                         geom = self.subtract_poly_from_geo(geom,
                         geom = self.subtract_poly_from_geo(geom,
                                                            px - gapsize + lenx / 4,
                                                            px - gapsize + lenx / 4,
-                                                           ymin - gapsize,
+                                                           yymin - gapsize,
                                                            px + gapsize + lenx / 4,
                                                            px + gapsize + lenx / 4,
-                                                           ymax + gapsize)
+                                                           yymax + gapsize)
                         geom = self.subtract_poly_from_geo(geom,
                         geom = self.subtract_poly_from_geo(geom,
                                                            px - gapsize - lenx / 4,
                                                            px - gapsize - lenx / 4,
-                                                           ymin - gapsize,
+                                                           yymin - gapsize,
                                                            px + gapsize - lenx / 4,
                                                            px + gapsize - lenx / 4,
-                                                           ymax + gapsize)
+                                                           yymax + gapsize)
 
 
                     if gaps == '4' or gaps == 'LR':
                     if gaps == '4' or gaps == 'LR':
                         geom = self.subtract_poly_from_geo(geom,
                         geom = self.subtract_poly_from_geo(geom,
-                                                           xmin - gapsize,
+                                                           xxmin - gapsize,
                                                            py - gapsize,
                                                            py - gapsize,
-                                                           xmax + gapsize,
+                                                           xxmax + gapsize,
                                                            py + gapsize)
                                                            py + gapsize)
 
 
                     if gaps == '4' or gaps == 'TB':
                     if gaps == '4' or gaps == 'TB':
                         geom = self.subtract_poly_from_geo(geom,
                         geom = self.subtract_poly_from_geo(geom,
                                                            px - gapsize,
                                                            px - gapsize,
-                                                           ymin - gapsize,
+                                                           yymin - gapsize,
                                                            px + gapsize,
                                                            px + gapsize,
-                                                           ymax + gapsize)
+                                                           yymax + gapsize)
 
 
                 try:
                 try:
                     for g in geom:
                     for g in geom:
@@ -603,7 +602,7 @@ class CutOut(FlatCAMTool):
                 object_geo = unary_union(object_geo)
                 object_geo = unary_union(object_geo)
 
 
                 # for geo in object_geo:
                 # for geo in object_geo:
-                if isinstance(cutout_obj, FlatCAMGerber):
+                if cutout_obj.kind == 'gerber':
                     if isinstance(object_geo, MultiPolygon):
                     if isinstance(object_geo, MultiPolygon):
                         x0, y0, x1, y1 = object_geo.bounds
                         x0, y0, x1, y1 = object_geo.bounds
                         object_geo = box(x0, y0, x1, y1)
                         object_geo = box(x0, y0, x1, y1)
@@ -623,7 +622,7 @@ class CutOut(FlatCAMTool):
                     object_geo = [object_geo]
                     object_geo = [object_geo]
 
 
                 for geom_struct in object_geo:
                 for geom_struct in object_geo:
-                    if isinstance(cutout_obj, FlatCAMGerber):
+                    if cutout_obj.kind == 'gerber':
                         if margin >= 0:
                         if margin >= 0:
                             geom_struct = (geom_struct.buffer(margin + abs(dia / 2))).exterior
                             geom_struct = (geom_struct.buffer(margin + abs(dia / 2))).exterior
                         else:
                         else:
@@ -775,7 +774,7 @@ class CutOut(FlatCAMTool):
 
 
                 # if Gerber create a buffer at a distance
                 # if Gerber create a buffer at a distance
                 # if Geometry then cut through the geometry
                 # if Geometry then cut through the geometry
-                if isinstance(cutout_obj, FlatCAMGerber):
+                if cutout_obj.kind == 'gerber':
                     if margin >= 0:
                     if margin >= 0:
                         geo = geo.buffer(margin + abs(dia / 2))
                         geo = geo.buffer(margin + abs(dia / 2))
                     else:
                     else:
@@ -909,7 +908,7 @@ class CutOut(FlatCAMTool):
                                    "Select one and try again."))
                                    "Select one and try again."))
             return
             return
 
 
-        if not isinstance(cutout_obj, FlatCAMGerber):
+        if cutout_obj.kind != 'gerber':
             self.app.inform.emit('[ERROR_NOTCL] %s' %
             self.app.inform.emit('[ERROR_NOTCL] %s' %
                                  _("The selected object has to be of Gerber type.\n"
                                  _("The selected object has to be of Gerber type.\n"
                                    "Select a Gerber file and try again."))
                                    "Select a Gerber file and try again."))
@@ -988,11 +987,11 @@ class CutOut(FlatCAMTool):
 
 
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
             event_pos = event.pos
             event_pos = event.pos
-            event_is_dragging = event.is_dragging
+            # event_is_dragging = event.is_dragging
             right_button = 2
             right_button = 2
         else:
         else:
             event_pos = (event.xdata, event.ydata)
             event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
+            # event_is_dragging = self.app.plotcanvas.is_dragging
             right_button = 3
             right_button = 3
 
 
         try:
         try:
@@ -1038,11 +1037,11 @@ class CutOut(FlatCAMTool):
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
             event_pos = event.pos
             event_pos = event.pos
             event_is_dragging = event.is_dragging
             event_is_dragging = event.is_dragging
-            right_button = 2
+            # right_button = 2
         else:
         else:
             event_pos = (event.xdata, event.ydata)
             event_pos = (event.xdata, event.ydata)
             event_is_dragging = self.app.plotcanvas.is_dragging
             event_is_dragging = self.app.plotcanvas.is_dragging
-            right_button = 3
+            # right_button = 3
 
 
         try:
         try:
             x = float(event_pos[0])
             x = float(event_pos[0])
@@ -1159,13 +1158,17 @@ class CutOut(FlatCAMTool):
             if '+' in key_string:
             if '+' in key_string:
                 mod, __, key_text = key_string.rpartition('+')
                 mod, __, key_text = key_string.rpartition('+')
                 if mod.lower() == 'ctrl':
                 if mod.lower() == 'ctrl':
-                    modifiers = QtCore.Qt.ControlModifier
+                    # modifiers = QtCore.Qt.ControlModifier
+                    pass
                 elif mod.lower() == 'alt':
                 elif mod.lower() == 'alt':
-                    modifiers = QtCore.Qt.AltModifier
+                    # modifiers = QtCore.Qt.AltModifier
+                    pass
                 elif mod.lower() == 'shift':
                 elif mod.lower() == 'shift':
-                    modifiers = QtCore.Qt.ShiftModifier
+                    # modifiers = QtCore.Qt.ShiftModifier
+                    pass
                 else:
                 else:
-                    modifiers = QtCore.Qt.NoModifier
+                    # modifiers = QtCore.Qt.NoModifier
+                    pass
                 key = QtGui.QKeySequence(key_text)
                 key = QtGui.QKeySequence(key_text)
         # events from Vispy are of type KeyEvent
         # events from Vispy are of type KeyEvent
         else:
         else:
@@ -1203,7 +1206,8 @@ class CutOut(FlatCAMTool):
             geo = self.cutting_geo(pos=(l_x, l_y))
             geo = self.cutting_geo(pos=(l_x, l_y))
             self.draw_utility_geometry(geo=geo)
             self.draw_utility_geometry(geo=geo)
 
 
-    def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1):
+    @staticmethod
+    def subtract_poly_from_geo(solid_geo, x0, y0, x1, y1):
         """
         """
         Subtract polygon made from points from the given object.
         Subtract polygon made from points from the given object.
         This only operates on the paths in the original geometry,
         This only operates on the paths in the original geometry,
@@ -1270,8 +1274,9 @@ def flatten(geometry):
 
 
 def recursive_bounds(geometry):
 def recursive_bounds(geometry):
     """
     """
-    Returns coordinates of rectangular bounds
-    of geometry: (xmin, ymin, xmax, ymax).
+
+    :param geometry:    a iterable object that holds geometry
+    :return:            Returns coordinates of rectangular bounds of geometry: (xmin, ymin, xmax, ymax).
     """
     """
 
 
     # now it can get bounds for nested lists of objects
     # now it can get bounds for nested lists of objects

+ 3 - 4
flatcamTools/ToolDblSided.py

@@ -3,7 +3,6 @@ from PyQt5 import QtWidgets, QtCore
 
 
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry, FCButton, FCComboBox
 from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry, FCButton, FCComboBox
-from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon, FlatCAMGeometry
 
 
 from numpy import Inf
 from numpy import Inf
 
 
@@ -658,7 +657,7 @@ class DblSidedTool(FlatCAMTool):
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
             return
             return
 
 
-        if not isinstance(fcobj, FlatCAMGerber):
+        if fcobj.kind != 'gerber':
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
             return
             return
 
 
@@ -701,7 +700,7 @@ class DblSidedTool(FlatCAMTool):
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Excellon object loaded ..."))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Excellon object loaded ..."))
             return
             return
 
 
-        if not isinstance(fcobj, FlatCAMExcellon):
+        if fcobj.kind != 'excellon':
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
             return
             return
 
 
@@ -745,7 +744,7 @@ class DblSidedTool(FlatCAMTool):
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Geometry object loaded ..."))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Geometry object loaded ..."))
             return
             return
 
 
-        if not isinstance(fcobj, FlatCAMGeometry):
+        if fcobj.kind != 'geometry':
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
             return
             return
 
 

+ 6 - 7
flatcamTools/ToolMove.py

@@ -8,7 +8,6 @@
 from PyQt5 import QtWidgets, QtCore
 from PyQt5 import QtWidgets, QtCore
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.VisPyVisuals import *
 from flatcamGUI.VisPyVisuals import *
-from FlatCAMObj import FlatCAMGerber
 
 
 from copy import copy
 from copy import copy
 import logging
 import logging
@@ -128,7 +127,7 @@ class ToolMove(FlatCAMTool):
                 pos_canvas = self.app.plotcanvas.translate_coords(event_pos)
                 pos_canvas = self.app.plotcanvas.translate_coords(event_pos)
 
 
                 # if GRID is active we need to get the snapped positions
                 # if GRID is active we need to get the snapped positions
-                if self.app.grid_status() == True:
+                if self.app.grid_status():
                     pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                     pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                 else:
                 else:
                     pos = pos_canvas
                     pos = pos_canvas
@@ -148,7 +147,7 @@ class ToolMove(FlatCAMTool):
                     self.delete_shape()
                     self.delete_shape()
 
 
                     # if GRID is active we need to get the snapped positions
                     # if GRID is active we need to get the snapped positions
-                    if self.app.grid_status() == True:
+                    if self.app.grid_status():
                         pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                         pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                     else:
                     else:
                         pos = pos_canvas
                         pos = pos_canvas
@@ -171,7 +170,7 @@ class ToolMove(FlatCAMTool):
                                 # remove any mark aperture shape that may be displayed
                                 # remove any mark aperture shape that may be displayed
                                 for sel_obj in obj_list:
                                 for sel_obj in obj_list:
                                     # if the Gerber mark shapes are enabled they need to be disabled before move
                                     # if the Gerber mark shapes are enabled they need to be disabled before move
-                                    if isinstance(sel_obj, FlatCAMGerber):
+                                    if sel_obj.kind == 'gerber':
                                         sel_obj.ui.aperture_table_visibility_cb.setChecked(False)
                                         sel_obj.ui.aperture_table_visibility_cb.setChecked(False)
 
 
                                     try:
                                     try:
@@ -198,8 +197,8 @@ class ToolMove(FlatCAMTool):
                                     elif sel_obj.kind == 'excellon':
                                     elif sel_obj.kind == 'excellon':
                                         sel_obj.source_file = self.app.export_excellon(
                                         sel_obj.source_file = self.app.export_excellon(
                                             obj_name=out_name, filename=None, local_use=sel_obj, use_thread=False)
                                             obj_name=out_name, filename=None, local_use=sel_obj, use_thread=False)
-                            except Exception as e:
-                                log.debug('[ERROR_NOTCL] %s --> %s' % ('ToolMove.on_left_click()', str(e)))
+                            except Exception as err:
+                                log.debug('[ERROR_NOTCL] %s --> %s' % ('ToolMove.on_left_click()', str(err)))
                                 return "fail"
                                 return "fail"
 
 
                             # time to plot the moved objects
                             # time to plot the moved objects
@@ -249,7 +248,7 @@ class ToolMove(FlatCAMTool):
         pos_canvas = self.app.plotcanvas.translate_coords((x, y))
         pos_canvas = self.app.plotcanvas.translate_coords((x, y))
 
 
         # if GRID is active we need to get the snapped positions
         # if GRID is active we need to get the snapped positions
-        if self.app.grid_status() == True:
+        if self.app.grid_status():
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
         else:
         else:
             pos = pos_canvas
             pos = pos_canvas

+ 26 - 26
flatcamTools/ToolNCC.py

@@ -12,7 +12,7 @@ from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTabl
     FCComboBox, OptionalInputSection
     FCComboBox, OptionalInputSection
 from flatcamParsers.ParseGerber import Gerber
 from flatcamParsers.ParseGerber import Gerber
 
 
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 
 
 from copy import deepcopy
 from copy import deepcopy
 
 
@@ -1987,7 +1987,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             for poly in env_obj:
             for poly in env_obj:
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
                 geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
                 geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
             bounding_box = cascaded_union(geo_buff_list)
             bounding_box = cascaded_union(geo_buff_list)
         elif ncc_select == _("Reference Object"):
         elif ncc_select == _("Reference Object"):
@@ -1996,7 +1996,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 for poly in env_obj:
                 for poly in env_obj:
                     if self.app.abort_flag:
                     if self.app.abort_flag:
                         # graceful abort requested by the user
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
                     geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
 
 
                 bounding_box = cascaded_union(geo_buff_list)
                 bounding_box = cascaded_union(geo_buff_list)
@@ -2090,7 +2090,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             if isinstance(geo_elem, Polygon):
                             if isinstance(geo_elem, Polygon):
                                 for ring in self.poly2rings(geo_elem):
                                 for ring in self.poly2rings(geo_elem):
@@ -2312,7 +2312,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
 
                 # provide the app with a way to process the GUI events when in a blocking loop
                 # provide the app with a way to process the GUI events when in a blocking loop
                 QtWidgets.QApplication.processEvents()
                 QtWidgets.QApplication.processEvents()
@@ -2377,7 +2377,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             # clean the polygon
                             # clean the polygon
                             p = p.buffer(0)
                             p = p.buffer(0)
@@ -2595,7 +2595,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
 
                 # provide the app with a way to process the GUI events when in a blocking loop
                 # provide the app with a way to process the GUI events when in a blocking loop
                 QtWidgets.QApplication.processEvents()
                 QtWidgets.QApplication.processEvents()
@@ -2644,7 +2644,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                     if self.app.abort_flag:
                     if self.app.abort_flag:
                         # graceful abort requested by the user
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     try:
                     try:
                         area = area.difference(poly)
                         area = area.difference(poly)
                     except Exception:
                     except Exception:
@@ -2674,7 +2674,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                         for p in area.geoms:
                         for p in area.geoms:
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             # clean the polygon
                             # clean the polygon
                             p = p.buffer(0)
                             p = p.buffer(0)
@@ -2753,7 +2753,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                         if self.app.abort_flag:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
 
                         # check if there is a geometry at all in the cleared geometry
                         # check if there is a geometry at all in the cleared geometry
                         if cleared_geo:
                         if cleared_geo:
@@ -2771,7 +2771,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                             for p in cleared_area:
                             for p in cleared_area:
                                 if self.app.abort_flag:
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
 
                                 poly = p.buffer(buffer_value)
                                 poly = p.buffer(buffer_value)
                                 cleared_by_last_tool.append(poly)
                                 cleared_by_last_tool.append(poly)
@@ -2836,7 +2836,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     app_obj.new_object("geometry", name, gen_clear_area_rest)
                     app_obj.new_object("geometry", name, gen_clear_area_rest)
                 else:
                 else:
                     app_obj.new_object("geometry", name, gen_clear_area)
                     app_obj.new_object("geometry", name, gen_clear_area)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 if run_threaded:
                 if run_threaded:
                     proc.done()
                     proc.done()
                 return
                 return
@@ -2999,7 +2999,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             for poly in geo_n:
             for poly in geo_n:
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
                 geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
                 geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
 
 
             bounding_box = cascaded_union(geo_buff_list)
             bounding_box = cascaded_union(geo_buff_list)
@@ -3017,7 +3017,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 for poly in geo_n:
                 for poly in geo_n:
                     if self.app.abort_flag:
                     if self.app.abort_flag:
                         # graceful abort requested by the user
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
                     geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
 
 
                 bounding_box = cascaded_union(geo_buff_list)
                 bounding_box = cascaded_union(geo_buff_list)
@@ -3141,7 +3141,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                                 if self.app.abort_flag:
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
 
                                 if isinstance(geo_elem, Polygon):
                                 if isinstance(geo_elem, Polygon):
                                     for ring in self.poly2rings(geo_elem):
                                     for ring in self.poly2rings(geo_elem):
@@ -3242,7 +3242,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
 
                 # provide the app with a way to process the GUI events when in a blocking loop
                 # provide the app with a way to process the GUI events when in a blocking loop
                 QtWidgets.QApplication.processEvents()
                 QtWidgets.QApplication.processEvents()
@@ -3283,7 +3283,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             # clean the polygon
                             # clean the polygon
                             p = p.buffer(0)
                             p = p.buffer(0)
@@ -3520,7 +3520,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                                 if self.app.abort_flag:
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
 
                                 if isinstance(geo_elem, Polygon):
                                 if isinstance(geo_elem, Polygon):
                                     for ring in self.poly2rings(geo_elem):
                                     for ring in self.poly2rings(geo_elem):
@@ -3614,7 +3614,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
             if self.app.abort_flag:
             if self.app.abort_flag:
                 # graceful abort requested by the user
                 # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+                raise grace
 
 
             if type(empty) is Polygon:
             if type(empty) is Polygon:
                 empty = MultiPolygon([empty])
                 empty = MultiPolygon([empty])
@@ -3628,7 +3628,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             while sorted_tools:
             while sorted_tools:
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
 
                 tool = sorted_tools.pop(0)
                 tool = sorted_tools.pop(0)
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
@@ -3648,7 +3648,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                     if self.app.abort_flag:
                     if self.app.abort_flag:
                         # graceful abort requested by the user
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     try:
                     try:
                         area = area.difference(poly_r)
                         area = area.difference(poly_r)
                     except Exception:
                     except Exception:
@@ -3678,7 +3678,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                         for p in area.geoms:
                         for p in area.geoms:
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             # clean the polygon
                             # clean the polygon
                             p = p.buffer(0)
                             p = p.buffer(0)
@@ -3754,7 +3754,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                         if self.app.abort_flag:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
 
                         # check if there is a geometry at all in the cleared geometry
                         # check if there is a geometry at all in the cleared geometry
                         if cleared_geo:
                         if cleared_geo:
@@ -3772,7 +3772,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                             for p in cleared_area:
                             for p in cleared_area:
                                 if self.app.abort_flag:
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
 
                                 r_poly = p.buffer(buffer_value)
                                 r_poly = p.buffer(buffer_value)
                                 cleared_by_last_tool.append(r_poly)
                                 cleared_by_last_tool.append(r_poly)
@@ -3833,7 +3833,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     app_obj.new_object("geometry", name, gen_clear_area_rest, plot=plot)
                     app_obj.new_object("geometry", name, gen_clear_area_rest, plot=plot)
                 else:
                 else:
                     app_obj.new_object("geometry", name, gen_clear_area, plot=plot)
                     app_obj.new_object("geometry", name, gen_clear_area, plot=plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 if run_threaded:
                 if run_threaded:
                     proc.done()
                     proc.done()
                 return
                 return
@@ -3887,7 +3887,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
                     if self.app.abort_flag:
                     if self.app.abort_flag:
                         # graceful abort requested by the user
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
                     boundary = boundary.difference(el)
                     boundary = boundary.difference(el)
                     pol_nr += 1
                     pol_nr += 1
                     disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
                     disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))

+ 9 - 10
flatcamTools/ToolOptimal.py

@@ -9,8 +9,7 @@ from PyQt5 import QtWidgets, QtCore, QtGui
 
 
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import OptionalHideInputSection, FCTextArea, FCEntry, FCSpinner, FCCheckBox, FCComboBox
 from flatcamGUI.GUIElements import OptionalHideInputSection, FCTextArea, FCEntry, FCSpinner, FCCheckBox, FCComboBox
-from FlatCAMObj import FlatCAMGerber
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 
 
 from shapely.geometry import MultiPolygon
 from shapely.geometry import MultiPolygon
 from shapely.ops import nearest_points
 from shapely.ops import nearest_points
@@ -343,7 +342,7 @@ class ToolOptimal(FlatCAMTool):
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
             return
             return
 
 
-        if not isinstance(fcobj, FlatCAMGerber):
+        if fcobj.kind != 'gerber':
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated."))
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated."))
             return
             return
 
 
@@ -365,7 +364,7 @@ class ToolOptimal(FlatCAMTool):
                         for geo_el in fcobj.apertures[ap]['geometry']:
                         for geo_el in fcobj.apertures[ap]['geometry']:
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
                             if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
                                 total_geo.append(geo_el['solid'])
                                 total_geo.append(geo_el['solid'])
@@ -395,7 +394,7 @@ class ToolOptimal(FlatCAMTool):
                     for s_geo in total_geo[idx:]:
                     for s_geo in total_geo[idx:]:
                         if self.app.abort_flag:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
 
                         # minimize the number of distances by not taking into considerations those that are too small
                         # minimize the number of distances by not taking into considerations those that are too small
                         dist = geo.distance(s_geo)
                         dist = geo.distance(s_geo)
@@ -459,7 +458,7 @@ class ToolOptimal(FlatCAMTool):
             log.debug("ToolOptimal.on_locate_position() --> first try %s" % str(e))
             log.debug("ToolOptimal.on_locate_position() --> first try %s" % str(e))
             self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
             self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
                                  "((x0, y0), (x1, y1)).")
                                  "((x0, y0), (x1, y1)).")
-            return 'fail'
+            return
 
 
         try:
         try:
             loc_1 = loc[0]
             loc_1 = loc[0]
@@ -471,7 +470,7 @@ class ToolOptimal(FlatCAMTool):
             self.app.on_jump_to(custom_location=loc)
             self.app.on_jump_to(custom_location=loc)
         except Exception as e:
         except Exception as e:
             log.debug("ToolOptimal.on_locate_position() --> sec try %s" % str(e))
             log.debug("ToolOptimal.on_locate_position() --> sec try %s" % str(e))
-            return 'fail'
+            return
 
 
     def on_update_text(self, data):
     def on_update_text(self, data):
         txt = ''
         txt = ''
@@ -567,12 +566,12 @@ class ToolOptimal(FlatCAMTool):
             if self.selected_locations_text != '':
             if self.selected_locations_text != '':
                 loc = eval(self.selected_locations_text)
                 loc = eval(self.selected_locations_text)
             else:
             else:
-                return 'fail'
+                return
         except Exception as e:
         except Exception as e:
             log.debug("ToolOptimal.on_locate_sec_position() --> first try %s" % str(e))
             log.debug("ToolOptimal.on_locate_sec_position() --> first try %s" % str(e))
             self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
             self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
                                  "((x0, y0), (x1, y1)).")
                                  "((x0, y0), (x1, y1)).")
-            return 'fail'
+            return
 
 
         try:
         try:
             loc_1 = loc[0]
             loc_1 = loc[0]
@@ -584,7 +583,7 @@ class ToolOptimal(FlatCAMTool):
             self.app.on_jump_to(custom_location=loc)
             self.app.on_jump_to(custom_location=loc)
         except Exception as e:
         except Exception as e:
             log.debug("ToolOptimal.on_locate_sec_position() --> sec try %s" % str(e))
             log.debug("ToolOptimal.on_locate_sec_position() --> sec try %s" % str(e))
-            return 'fail'
+            return
 
 
     def reset_fields(self):
     def reset_fields(self):
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 8 - 10
flatcamTools/ToolPDF.py

@@ -8,7 +8,7 @@
 from PyQt5 import QtWidgets, QtCore
 from PyQt5 import QtWidgets, QtCore
 
 
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 
 
 from shapely.geometry import Point, Polygon, LineString, MultiPolygon
 from shapely.geometry import Point, Polygon, LineString, MultiPolygon
 from shapely.ops import unary_union
 from shapely.ops import unary_union
@@ -190,7 +190,7 @@ class ToolPDF(FlatCAMTool):
 
 
         if self.app.abort_flag:
         if self.app.abort_flag:
             # graceful abort requested by the user
             # graceful abort requested by the user
-            raise FlatCAMApp.GracefulException
+            raise grace
 
 
         with self.app.proc_container.new(_("Parsing PDF file ...")):
         with self.app.proc_container.new(_("Parsing PDF file ...")):
             with open(filename, "rb") as f:
             with open(filename, "rb") as f:
@@ -200,7 +200,7 @@ class ToolPDF(FlatCAMTool):
             for s in re.findall(self.stream_re, pdf):
             for s in re.findall(self.stream_re, pdf):
                 if self.app.abort_flag:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     # graceful abort requested by the user
-                    raise FlatCAMApp.GracefulException
+                    raise grace
 
 
                 stream_nr += 1
                 stream_nr += 1
                 log.debug(" PDF STREAM: %d\n" % stream_nr)
                 log.debug(" PDF STREAM: %d\n" % stream_nr)
@@ -291,7 +291,7 @@ class ToolPDF(FlatCAMTool):
     def layer_rendering_as_gerber(self, filename, ap_dict, layer_nr):
     def layer_rendering_as_gerber(self, filename, ap_dict, layer_nr):
         outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
         outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
 
 
-        def obj_init(grb_obj, app_obj):
+        def obj_init(grb_obj):
 
 
             grb_obj.apertures = ap_dict
             grb_obj.apertures = ap_dict
 
 
@@ -404,7 +404,7 @@ class ToolPDF(FlatCAMTool):
                     for object_name in self.pdf_parsed:
                     for object_name in self.pdf_parsed:
                         if self.app.abort_flag:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
 
                         filename = deepcopy(self.pdf_parsed[object_name]['filename'])
                         filename = deepcopy(self.pdf_parsed[object_name]['filename'])
                         pdf_content = deepcopy(self.pdf_parsed[object_name]['pdf'])
                         pdf_content = deepcopy(self.pdf_parsed[object_name]['pdf'])
@@ -412,7 +412,7 @@ class ToolPDF(FlatCAMTool):
                         for k in pdf_content:
                         for k in pdf_content:
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             ap_dict = pdf_content[k]
                             ap_dict = pdf_content[k]
                             if ap_dict:
                             if ap_dict:
@@ -493,7 +493,7 @@ class ToolPDF(FlatCAMTool):
         for pline in lines:
         for pline in lines:
             if self.app.abort_flag:
             if self.app.abort_flag:
                 # graceful abort requested by the user
                 # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+                raise grace
 
 
             line_nr += 1
             line_nr += 1
             log.debug("line %d: %s" % (line_nr, pline))
             log.debug("line %d: %s" % (line_nr, pline))
@@ -868,7 +868,6 @@ class ToolPDF(FlatCAMTool):
                                 new_el['solid'] = pdf_geo
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
-                        found_aperture = None
                     else:
                     else:
                         if str(aperture) in apertures_dict.keys():
                         if str(aperture) in apertures_dict.keys():
                             aperture += 1
                             aperture += 1
@@ -1231,7 +1230,6 @@ class ToolPDF(FlatCAMTool):
                                 new_el['solid'] = pdf_geo
                                 new_el['solid'] = pdf_geo
                                 new_el['follow'] = pdf_geo.exterior
                                 new_el['follow'] = pdf_geo.exterior
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
-                        found_aperture = None
                     else:
                     else:
                         if str(aperture) in apertures_dict.keys():
                         if str(aperture) in apertures_dict.keys():
                             aperture += 1
                             aperture += 1
@@ -1355,7 +1353,7 @@ class ToolPDF(FlatCAMTool):
 
 
         if self.app.abort_flag:
         if self.app.abort_flag:
             # graceful abort requested by the user
             # graceful abort requested by the user
-            raise FlatCAMApp.GracefulException
+            raise grace
 
 
         return object_dict
         return object_dict
 
 

+ 23 - 23
flatcamTools/ToolPaint.py

@@ -14,7 +14,7 @@ from copy import deepcopy
 from flatcamParsers.ParseGerber import Gerber
 from flatcamParsers.ParseGerber import Gerber
 from camlib import Geometry, FlatCAMRTreeStorage
 from camlib import Geometry, FlatCAMRTreeStorage
 from flatcamGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDialog, RadioSet, FCButton, FCComboBox
 from flatcamGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDialog, RadioSet, FCButton, FCComboBox
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 
 
 from shapely.geometry import base, Polygon, MultiPolygon, LinearRing, Point
 from shapely.geometry import base, Polygon, MultiPolygon, LinearRing, Point
 from shapely.ops import cascaded_union, unary_union, linemerge
 from shapely.ops import cascaded_union, unary_union, linemerge
@@ -1836,7 +1836,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                                            contour=cont,
                                            contour=cont,
                                            connect=conn,
                                            connect=conn,
                                            prog_plot=prog_plot)
                                            prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
                 return "fail"
             except Exception as ee:
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Standard --> %s" % str(ee))
                 log.debug("ToolPaint.paint_polygon_worker() Standard --> %s" % str(ee))
@@ -1850,7 +1850,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                                             contour=cont,
                                             contour=cont,
                                             connect=conn,
                                             connect=conn,
                                             prog_plot=prog_plot)
                                             prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
                 return "fail"
             except Exception as ee:
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Seed --> %s" % str(ee))
                 log.debug("ToolPaint.paint_polygon_worker() Seed --> %s" % str(ee))
@@ -1864,7 +1864,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                                             contour=cont,
                                             contour=cont,
                                             connect=conn,
                                             connect=conn,
                                             prog_plot=prog_plot)
                                             prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
                 return "fail"
             except Exception as ee:
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Lines --> %s" % str(ee))
                 log.debug("ToolPaint.paint_polygon_worker() Lines --> %s" % str(ee))
@@ -2015,7 +2015,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                 #                                  contour=cont,
                 #                                  contour=cont,
                 #                                  connect=conn,
                 #                                  connect=conn,
                 #                                  prog_plot=prog_plot)
                 #                                  prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
                 return "fail"
             except Exception as ee:
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Laser Lines --> %s" % str(ee))
                 log.debug("ToolPaint.paint_polygon_worker() Laser Lines --> %s" % str(ee))
@@ -2052,7 +2052,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                                                    contour=cont,
                                                    contour=cont,
                                                    connect=conn,
                                                    connect=conn,
                                                    prog_plot=prog_plot)
                                                    prog_plot=prog_plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
                 return "fail"
             except Exception as ee:
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Combo --> %s" % str(ee))
                 log.debug("ToolPaint.paint_polygon_worker() Combo --> %s" % str(ee))
@@ -2199,7 +2199,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
                         geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                         geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
                                                             cont=cont, paint_method=paint_method, obj=obj,
                                                             prog_plot=prog_plot)
                                                             prog_plot=prog_plot)
@@ -2217,7 +2217,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     QtWidgets.QApplication.processEvents()
                     QtWidgets.QApplication.processEvents()
                     if self.app.abort_flag:
                     if self.app.abort_flag:
                         # graceful abort requested by the user
                         # graceful abort requested by the user
-                        raise FlatCAMApp.GracefulException
+                        raise grace
 
 
                     geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                     geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                         cont=cont, paint_method=paint_method, obj=obj,
                                                         cont=cont, paint_method=paint_method, obj=obj,
@@ -2230,7 +2230,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     for x in cp:
                     for x in cp:
                         total_geometry += list(x.get_objects())
                         total_geometry += list(x.get_objects())
                     final_solid_geometry += total_geometry
                     final_solid_geometry += total_geometry
-            except FlatCAMApp.GracefulException:
+            except grace:
                 return "fail"
                 return "fail"
             except Exception as e:
             except Exception as e:
                 log.debug("Could not Paint the polygons. %s" % str(e))
                 log.debug("Could not Paint the polygons. %s" % str(e))
@@ -2305,7 +2305,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         def job_thread(app_obj):
         def job_thread(app_obj):
             try:
             try:
                 ret = app_obj.new_object("geometry", name, job_init, plot=plot)
                 ret = app_obj.new_object("geometry", name, job_init, plot=plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 proc.done()
                 proc.done()
                 return
                 return
             except Exception as er:
             except Exception as er:
@@ -2376,7 +2376,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             """
             """
             if self.app.abort_flag:
             if self.app.abort_flag:
                 # graceful abort requested by the user
                 # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+                raise grace
 
 
             if geometry is None:
             if geometry is None:
                 return
                 return
@@ -2517,7 +2517,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                             QtWidgets.QApplication.processEvents()
                             QtWidgets.QApplication.processEvents()
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
@@ -2542,7 +2542,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
 
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
                                                             cont=cont, paint_method=paint_method, obj=obj,
@@ -2705,7 +2705,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                             QtWidgets.QApplication.processEvents()
                             QtWidgets.QApplication.processEvents()
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
                                                                 prog_plot=prog_plot)
                                                                 prog_plot=prog_plot)
@@ -2723,7 +2723,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
 
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
                                                             cont=cont, paint_method=paint_method, obj=obj,
@@ -2735,7 +2735,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         for x in cp:
                         for x in cp:
                             cleared_geo += list(x.get_objects())
                             cleared_geo += list(x.get_objects())
                         final_solid_geometry += cleared_geo
                         final_solid_geometry += cleared_geo
-                except FlatCAMApp.GracefulException:
+                except grace:
                     return "fail"
                     return "fail"
                 except Exception as e:
                 except Exception as e:
                     log.debug("Could not Paint the polygons. %s" % str(e))
                     log.debug("Could not Paint the polygons. %s" % str(e))
@@ -2815,7 +2815,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot)
                     ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot)
                 else:
                 else:
                     ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot)
                     ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 proc.done()
                 proc.done()
                 return
                 return
             except Exception as err:
             except Exception as err:
@@ -2873,7 +2873,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             """
             """
             if self.app.abort_flag:
             if self.app.abort_flag:
                 # graceful abort requested by the user
                 # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+                raise grace
 
 
             if geometry is None:
             if geometry is None:
                 return
                 return
@@ -3015,7 +3015,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                             QtWidgets.QApplication.processEvents()
                             QtWidgets.QApplication.processEvents()
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
@@ -3040,7 +3040,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
 
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
                                                             cont=cont, paint_method=paint_method, obj=obj,
@@ -3193,7 +3193,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                             QtWidgets.QApplication.processEvents()
                             QtWidgets.QApplication.processEvents()
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
+                                raise grace
 
 
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                             geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
                                                                 cont=cont, paint_method=paint_method, obj=obj,
@@ -3218,7 +3218,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         QtWidgets.QApplication.processEvents()
                         QtWidgets.QApplication.processEvents()
                         if self.app.abort_flag:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             # graceful abort requested by the user
-                            raise FlatCAMApp.GracefulException
+                            raise grace
 
 
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                         geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn,
                                                             cont=cont, paint_method=paint_method, obj=obj,
                                                             cont=cont, paint_method=paint_method, obj=obj,
@@ -3312,7 +3312,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot)
                     ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot)
                 else:
                 else:
                     ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot)
                     ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot)
-            except FlatCAMApp.GracefulException:
+            except grace:
                 proc.done()
                 proc.done()
                 return
                 return
             except Exception as err:
             except Exception as err:

+ 18 - 20
flatcamTools/ToolPanelize.py

@@ -9,10 +9,8 @@ from PyQt5 import QtWidgets, QtGui, QtCore
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 
 
 from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCCheckBox, OptionalInputSection, FCComboBox
 from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCCheckBox, OptionalInputSection, FCComboBox
-from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber, FlatCAMExcellon
-import FlatCAMApp
+from FlatCAMCommon import GracefulException as grace
 from copy import deepcopy
 from copy import deepcopy
-# from ObjectCollection import *
 import numpy as np
 import numpy as np
 
 
 import shapely.affinity as affinity
 import shapely.affinity as affinity
@@ -480,13 +478,13 @@ class Panelize(FlatCAMTool):
                     rows -= 1
                     rows -= 1
                     panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
                     panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
 
 
-        if isinstance(panel_obj, FlatCAMExcellon) or isinstance(panel_obj, FlatCAMGeometry):
+        if panel_obj.kind == 'excellon' or panel_obj.kind == 'geometry':
             # make a copy of the panelized Excellon or Geometry tools
             # make a copy of the panelized Excellon or Geometry tools
             copied_tools = {}
             copied_tools = {}
             for tt, tt_val in list(panel_obj.tools.items()):
             for tt, tt_val in list(panel_obj.tools.items()):
                 copied_tools[tt] = deepcopy(tt_val)
                 copied_tools[tt] = deepcopy(tt_val)
 
 
-        if isinstance(panel_obj, FlatCAMGerber):
+        if panel_obj.kind == 'gerber':
             # make a copy of the panelized Gerber apertures
             # make a copy of the panelized Gerber apertures
             copied_apertures = {}
             copied_apertures = {}
             for tt, tt_val in list(panel_obj.apertures.items()):
             for tt, tt_val in list(panel_obj.apertures.items()):
@@ -525,7 +523,7 @@ class Panelize(FlatCAMTool):
                                 for tool_dict in panel_obj.drills:
                                 for tool_dict in panel_obj.drills:
                                     if self.app.abort_flag:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
+                                        raise grace
 
 
                                     point_offseted = affinity.translate(tool_dict['point'], currentx, currenty)
                                     point_offseted = affinity.translate(tool_dict['point'], currentx, currenty)
                                     obj_fin.drills.append(
                                     obj_fin.drills.append(
@@ -550,7 +548,7 @@ class Panelize(FlatCAMTool):
                                 for tool_dict in panel_obj.slots:
                                 for tool_dict in panel_obj.slots:
                                     if self.app.abort_flag:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
+                                        raise grace
 
 
                                     start_offseted = affinity.translate(tool_dict['start'], currentx, currenty)
                                     start_offseted = affinity.translate(tool_dict['start'], currentx, currenty)
                                     stop_offseted = affinity.translate(tool_dict['stop'], currentx, currenty)
                                     stop_offseted = affinity.translate(tool_dict['stop'], currentx, currenty)
@@ -600,20 +598,20 @@ class Panelize(FlatCAMTool):
                     obj_fin.solid_geometry = []
                     obj_fin.solid_geometry = []
 
 
                     # create the initial structure on which to create the panel
                     # create the initial structure on which to create the panel
-                    if isinstance(panel_obj, FlatCAMGeometry):
+                    if panel_obj.kind == 'geometry':
                         obj_fin.multigeo = panel_obj.multigeo
                         obj_fin.multigeo = panel_obj.multigeo
                         obj_fin.tools = copied_tools
                         obj_fin.tools = copied_tools
                         if panel_obj.multigeo is True:
                         if panel_obj.multigeo is True:
                             for tool in panel_obj.tools:
                             for tool in panel_obj.tools:
                                 obj_fin.tools[tool]['solid_geometry'][:] = []
                                 obj_fin.tools[tool]['solid_geometry'][:] = []
-                    elif isinstance(panel_obj, FlatCAMGerber):
+                    elif panel_obj.kind == 'gerber':
                         obj_fin.apertures = copied_apertures
                         obj_fin.apertures = copied_apertures
                         for ap in obj_fin.apertures:
                         for ap in obj_fin.apertures:
                             obj_fin.apertures[ap]['geometry'] = []
                             obj_fin.apertures[ap]['geometry'] = []
 
 
                     # find the number of polygons in the source solid_geometry
                     # find the number of polygons in the source solid_geometry
                     geo_len = 0
                     geo_len = 0
-                    if isinstance(panel_obj, FlatCAMGeometry):
+                    if panel_obj.kind == 'geometry':
                         if panel_obj.multigeo is True:
                         if panel_obj.multigeo is True:
                             for tool in panel_obj.tools:
                             for tool in panel_obj.tools:
                                 try:
                                 try:
@@ -625,7 +623,7 @@ class Panelize(FlatCAMTool):
                                 geo_len = len(panel_obj.solid_geometry)
                                 geo_len = len(panel_obj.solid_geometry)
                             except TypeError:
                             except TypeError:
                                 geo_len = 1
                                 geo_len = 1
-                    elif isinstance(panel_obj, FlatCAMGerber):
+                    elif panel_obj.kind == 'gerber':
                         for ap in panel_obj.apertures:
                         for ap in panel_obj.apertures:
                             if 'geometry' in panel_obj.apertures[ap]:
                             if 'geometry' in panel_obj.apertures[ap]:
                                 try:
                                 try:
@@ -641,12 +639,12 @@ class Panelize(FlatCAMTool):
                             element += 1
                             element += 1
                             old_disp_number = 0
                             old_disp_number = 0
 
 
-                            if isinstance(panel_obj, FlatCAMGeometry):
+                            if panel_obj.kind == 'geometry':
                                 if panel_obj.multigeo is True:
                                 if panel_obj.multigeo is True:
                                     for tool in panel_obj.tools:
                                     for tool in panel_obj.tools:
                                         if self.app.abort_flag:
                                         if self.app.abort_flag:
                                             # graceful abort requested by the user
                                             # graceful abort requested by the user
-                                            raise FlatCAMApp.GracefulException
+                                            raise grace
 
 
                                         # geo = translate_recursion(panel_obj.tools[tool]['solid_geometry'])
                                         # geo = translate_recursion(panel_obj.tools[tool]['solid_geometry'])
                                         # if isinstance(geo, list):
                                         # if isinstance(geo, list):
@@ -678,7 +676,7 @@ class Panelize(FlatCAMTool):
                                     #     obj_fin.solid_geometry.append(geo)
                                     #     obj_fin.solid_geometry.append(geo)
                                     if self.app.abort_flag:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
+                                        raise grace
 
 
                                     try:
                                     try:
                                         # calculate the number of polygons
                                         # calculate the number of polygons
@@ -690,7 +688,7 @@ class Panelize(FlatCAMTool):
                                         for geo_el in panel_obj.solid_geometry:
                                         for geo_el in panel_obj.solid_geometry:
                                             if self.app.abort_flag:
                                             if self.app.abort_flag:
                                                 # graceful abort requested by the user
                                                 # graceful abort requested by the user
-                                                raise FlatCAMApp.GracefulException
+                                                raise grace
 
 
                                             trans_geo = translate_recursion(geo_el)
                                             trans_geo = translate_recursion(geo_el)
                                             obj_fin.solid_geometry.append(trans_geo)
                                             obj_fin.solid_geometry.append(trans_geo)
@@ -715,13 +713,13 @@ class Panelize(FlatCAMTool):
                                 #     obj_fin.solid_geometry.append(geo)
                                 #     obj_fin.solid_geometry.append(geo)
                                 if self.app.abort_flag:
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
                                     # graceful abort requested by the user
-                                    raise FlatCAMApp.GracefulException
+                                    raise grace
 
 
                                 try:
                                 try:
                                     for geo_el in panel_obj.solid_geometry:
                                     for geo_el in panel_obj.solid_geometry:
                                         if self.app.abort_flag:
                                         if self.app.abort_flag:
                                             # graceful abort requested by the user
                                             # graceful abort requested by the user
-                                            raise FlatCAMApp.GracefulException
+                                            raise grace
 
 
                                         trans_geo = translate_recursion(geo_el)
                                         trans_geo = translate_recursion(geo_el)
                                         obj_fin.solid_geometry.append(trans_geo)
                                         obj_fin.solid_geometry.append(trans_geo)
@@ -732,7 +730,7 @@ class Panelize(FlatCAMTool):
                                 for apid in panel_obj.apertures:
                                 for apid in panel_obj.apertures:
                                     if self.app.abort_flag:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
+                                        raise grace
                                     if 'geometry' in panel_obj.apertures[apid]:
                                     if 'geometry' in panel_obj.apertures[apid]:
                                         try:
                                         try:
                                             # calculate the number of polygons
                                             # calculate the number of polygons
@@ -743,7 +741,7 @@ class Panelize(FlatCAMTool):
                                         for el in panel_obj.apertures[apid]['geometry']:
                                         for el in panel_obj.apertures[apid]['geometry']:
                                             if self.app.abort_flag:
                                             if self.app.abort_flag:
                                                 # graceful abort requested by the user
                                                 # graceful abort requested by the user
-                                                raise FlatCAMApp.GracefulException
+                                                raise grace
 
 
                                             new_el = {}
                                             new_el = {}
                                             if 'solid' in el:
                                             if 'solid' in el:
@@ -786,7 +784,7 @@ class Panelize(FlatCAMTool):
                     self.app.proc_container.update_view_text('')
                     self.app.proc_container.update_view_text('')
 
 
                 self.app.inform.emit('%s: %d' % (_("Generating panel... Spawning copies"), (int(rows * columns))))
                 self.app.inform.emit('%s: %d' % (_("Generating panel... Spawning copies"), (int(rows * columns))))
-                if isinstance(panel_obj, FlatCAMExcellon):
+                if panel_obj.kind == 'excellon':
                     self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
                     self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
                 else:
                 else:
                     self.app.new_object(panel_type, self.outname, job_init_geometry, plot=True, autoselected=True)
                     self.app.new_object(panel_type, self.outname, job_init_geometry, plot=True, autoselected=True)

+ 5 - 4
flatcamTools/ToolSolderPaste.py

@@ -11,7 +11,6 @@ from flatcamGUI.GUIElements import FCComboBox, FCEntry, FCTable, \
     FCInputDialog, FCDoubleSpinner, FCSpinner, FCFileSaveDialog
     FCInputDialog, FCDoubleSpinner, FCSpinner, FCFileSaveDialog
 from FlatCAMApp import log
 from FlatCAMApp import log
 from camlib import distance
 from camlib import distance
-from FlatCAMObj import FlatCAMCNCjob
 from flatcamEditors.FlatCAMTextEditor import TextEditor
 from flatcamEditors.FlatCAMTextEditor import TextEditor
 
 
 from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5 import QtGui, QtCore, QtWidgets
@@ -506,7 +505,8 @@ class SolderPaste(FlatCAMTool):
         self.flat_geometry = []
         self.flat_geometry = []
 
 
         # action to be added in the combobox context menu
         # action to be added in the combobox context menu
-        self.combo_context_del_action = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/trash16.png'), _("Delete Object"))
+        self.combo_context_del_action = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/trash16.png'),
+                                                          _("Delete Object"))
 
 
         # ## Signals
         # ## Signals
         self.combo_context_del_action.triggered.connect(self.on_delete_object)
         self.combo_context_del_action.triggered.connect(self.on_delete_object)
@@ -966,6 +966,7 @@ class SolderPaste(FlatCAMTool):
                 self.build_ui()
                 self.build_ui()
                 return
                 return
             else:
             else:
+                old_tool_dia = ''
                 # identify the old tool_dia and restore the text in tool table
                 # identify the old tool_dia and restore the text in tool table
                 for k, v in self.tooltable_tools.items():
                 for k, v in self.tooltable_tools.items():
                     if k == tooluid:
                     if k == tooluid:
@@ -1332,8 +1333,8 @@ class SolderPaste(FlatCAMTool):
 
 
         # Object initialization function for app.new_object()
         # Object initialization function for app.new_object()
         # RUNNING ON SEPARATE THREAD!
         # RUNNING ON SEPARATE THREAD!
-        def job_init(job_obj, app_obj):
-            assert isinstance(job_obj, FlatCAMCNCjob), \
+        def job_init(job_obj):
+            assert job_obj.kind == 'cncjob', \
                 "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
                 "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
 
 
             # this turn on the FlatCAMCNCJob plot for multiple tools
             # this turn on the FlatCAMCNCJob plot for multiple tools

+ 14 - 19
flatcamTools/ToolTransform.py

@@ -8,7 +8,6 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, EvalEntry2
 from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, EvalEntry2
-from FlatCAMObj import FlatCAMCNCjob
 
 
 import gettext
 import gettext
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
@@ -681,7 +680,7 @@ class ToolTransform(FlatCAMTool):
                 try:
                 try:
                     # first get a bounding box to fit all
                     # first get a bounding box to fit all
                     for obj in obj_list:
                     for obj in obj_list:
-                        if isinstance(obj, FlatCAMCNCjob):
+                        if obj.kind == 'cncjob':
                             pass
                             pass
                         else:
                         else:
                             xmin, ymin, xmax, ymax = obj.bounds()
                             xmin, ymin, xmax, ymax = obj.bounds()
@@ -699,7 +698,7 @@ class ToolTransform(FlatCAMTool):
                     px = 0.5 * (xminimal + xmaximal)
                     px = 0.5 * (xminimal + xmaximal)
                     py = 0.5 * (yminimal + ymaximal)
                     py = 0.5 * (yminimal + ymaximal)
                     for sel_obj in obj_list:
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be rotated."))
                             self.app.inform.emit(_("CNCJob objects can't be rotated."))
                         else:
                         else:
                             sel_obj.rotate(-num, point=(px, py))
                             sel_obj.rotate(-num, point=(px, py))
@@ -735,7 +734,7 @@ class ToolTransform(FlatCAMTool):
                     else:
                     else:
                         # first get a bounding box to fit all
                         # first get a bounding box to fit all
                         for obj in obj_list:
                         for obj in obj_list:
-                            if isinstance(obj, FlatCAMCNCjob):
+                            if obj.kind == 'cncjob':
                                 pass
                                 pass
                             else:
                             else:
                                 xmin, ymin, xmax, ymax = obj.bounds()
                                 xmin, ymin, xmax, ymax = obj.bounds()
@@ -755,7 +754,7 @@ class ToolTransform(FlatCAMTool):
 
 
                     # execute mirroring
                     # execute mirroring
                     for sel_obj in obj_list:
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be mirrored/flipped."))
                             self.app.inform.emit(_("CNCJob objects can't be mirrored/flipped."))
                         else:
                         else:
                             if axis == 'X':
                             if axis == 'X':
@@ -803,7 +802,7 @@ class ToolTransform(FlatCAMTool):
                 try:
                 try:
                     # first get a bounding box to fit all
                     # first get a bounding box to fit all
                     for obj in obj_list:
                     for obj in obj_list:
-                        if isinstance(obj, FlatCAMCNCjob):
+                        if obj.kind == 'cncjob':
                             pass
                             pass
                         else:
                         else:
                             xmin, ymin, xmax, ymax = obj.bounds()
                             xmin, ymin, xmax, ymax = obj.bounds()
@@ -815,7 +814,7 @@ class ToolTransform(FlatCAMTool):
                     yminimal = min(yminlist)
                     yminimal = min(yminlist)
 
 
                     for sel_obj in obj_list:
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be skewed."))
                             self.app.inform.emit(_("CNCJob objects can't be skewed."))
                         else:
                         else:
                             if axis == 'X':
                             if axis == 'X':
@@ -842,15 +841,14 @@ class ToolTransform(FlatCAMTool):
         ymaxlist = []
         ymaxlist = []
 
 
         if not obj_list:
         if not obj_list:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("No object selected. Please Select an object to scale!"))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to scale!"))
             return
             return
         else:
         else:
             with self.app.proc_container.new(_("Applying Scale")):
             with self.app.proc_container.new(_("Applying Scale")):
                 try:
                 try:
                     # first get a bounding box to fit all
                     # first get a bounding box to fit all
                     for obj in obj_list:
                     for obj in obj_list:
-                        if isinstance(obj, FlatCAMCNCjob):
+                        if obj.kind == 'cncjob':
                             pass
                             pass
                         else:
                         else:
                             xmin, ymin, xmax, ymax = obj.bounds()
                             xmin, ymin, xmax, ymax = obj.bounds()
@@ -873,7 +871,7 @@ class ToolTransform(FlatCAMTool):
                         py = 0
                         py = 0
 
 
                     for sel_obj in obj_list:
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be scaled."))
                             self.app.inform.emit(_("CNCJob objects can't be scaled."))
                         else:
                         else:
                             sel_obj.scale(xfactor, yfactor, point=(px, py))
                             sel_obj.scale(xfactor, yfactor, point=(px, py))
@@ -883,8 +881,7 @@ class ToolTransform(FlatCAMTool):
                             self.app.object_changed.emit(sel_obj)
                             self.app.object_changed.emit(sel_obj)
                         sel_obj.plot()
                         sel_obj.plot()
 
 
-                    self.app.inform.emit('[success] %s %s %s...' %
-                                         (_('Scale on the'), str(axis), _('axis done')))
+                    self.app.inform.emit('[success] %s %s %s...' % (_('Scale on the'), str(axis), _('axis done')))
                 except Exception as e:
                 except Exception as e:
                     self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
                     self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
                                          (_("Due of"), str(e), _("action was not executed.")))
                                          (_("Due of"), str(e), _("action was not executed.")))
@@ -894,14 +891,13 @@ class ToolTransform(FlatCAMTool):
         obj_list = self.app.collection.get_selected()
         obj_list = self.app.collection.get_selected()
 
 
         if not obj_list:
         if not obj_list:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("No object selected. Please Select an object to offset!"))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to offset!"))
             return
             return
         else:
         else:
             with self.app.proc_container.new(_("Applying Offset")):
             with self.app.proc_container.new(_("Applying Offset")):
                 try:
                 try:
                     for sel_obj in obj_list:
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be offset."))
                             self.app.inform.emit(_("CNCJob objects can't be offset."))
                         else:
                         else:
                             if axis == 'X':
                             if axis == 'X':
@@ -915,8 +911,7 @@ class ToolTransform(FlatCAMTool):
                             self.app.object_changed.emit(sel_obj)
                             self.app.object_changed.emit(sel_obj)
                         sel_obj.plot()
                         sel_obj.plot()
 
 
-                    self.app.inform.emit('[success] %s %s %s...' %
-                                         (_('Offset on the'), str(axis), _('axis done')))
+                    self.app.inform.emit('[success] %s %s %s...' % (_('Offset on the'), str(axis), _('axis done')))
                 except Exception as e:
                 except Exception as e:
                     self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
                     self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
                                          (_("Due of"), str(e),  _("action was not executed.")))
                                          (_("Due of"), str(e),  _("action was not executed.")))
@@ -932,7 +927,7 @@ class ToolTransform(FlatCAMTool):
             with self.app.proc_container.new(_("Applying Buffer")):
             with self.app.proc_container.new(_("Applying Buffer")):
                 try:
                 try:
                     for sel_obj in obj_list:
                     for sel_obj in obj_list:
-                        if isinstance(sel_obj, FlatCAMCNCjob):
+                        if sel_obj.kind == 'cncjob':
                             self.app.inform.emit(_("CNCJob objects can't be buffered."))
                             self.app.inform.emit(_("CNCJob objects can't be buffered."))
                         elif sel_obj.kind.lower() == 'gerber':
                         elif sel_obj.kind.lower() == 'gerber':
                             sel_obj.buffer(value, join, factor)
                             sel_obj.buffer(value, join, factor)

Некоторые файлы не были показаны из-за большого количества измененных файлов