Przeglądaj źródła

- modified the Isolation Tool UI: now the tools can be reordered (if the order UI radio is set to 'no')

Marius Stanciu 5 lat temu
rodzic
commit
14425d1b25
2 zmienionych plików z 2670 dodań i 2586 usunięć
  1. 1 1
      CHANGELOG.md
  2. 2669 2585
      appTools/ToolIsolation.py

+ 1 - 1
CHANGELOG.md

@@ -19,7 +19,7 @@ CHANGELOG for FlatCAM beta
 - some updates in NCC Tool using code from Paint Tool
 - in Paint and NCC Tools made sure that using the key ESCAPE to cancel the tool will not create mouse events issues
 - some updates in Tcl commands Paint and CopperClear data dicts
-
+- modified the Isolation Tool UI: now the tools can be reordered (if the order UI radio is set to 'no')
 
 13.06.2020
 

+ 2669 - 2585
appTools/ToolIsolation.py

@@ -35,7 +35,6 @@ log = logging.getLogger('base')
 
 
 class ToolIsolation(AppTool, Gerber):
-    toolName = _("Isolation Tool")
 
     def __init__(self, app):
         self.app = app
@@ -44,3124 +43,3209 @@ class ToolIsolation(AppTool, Gerber):
         AppTool.__init__(self, app)
         Gerber.__init__(self, steps_per_circle=self.app.defaults["gerber_circle_steps"])
 
-        self.tools_frame = QtWidgets.QFrame()
-        self.tools_frame.setContentsMargins(0, 0, 0, 0)
-        self.layout.addWidget(self.tools_frame)
-        self.tools_box = QtWidgets.QVBoxLayout()
-        self.tools_box.setContentsMargins(0, 0, 0, 0)
-        self.tools_frame.setLayout(self.tools_box)
-
-        self.title_box = QtWidgets.QHBoxLayout()
-        self.tools_box.addLayout(self.title_box)
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = IsoUI(layout=self.layout, app=self.app)
 
-        # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        title_label.setToolTip(
-            _("Create a Geometry object with\n"
-              "toolpaths to cut around polygons.")
+        # #############################################################################
+        # ###################### Setup CONTEXT MENU ###################################
+        # #############################################################################
+        self.ui.tools_table.setupContextMenu()
+        self.ui.tools_table.addContextMenu(
+            _("Add"), self.on_add_tool_by_key, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")
+        )
+        self.ui.tools_table.addContextMenu(
+            _("Add from DB"), self.on_add_tool_by_key, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")
+        )
+        self.ui.tools_table.addContextMenu(
+            _("Delete"), lambda:
+            self.on_tool_delete(rows_to_delete=None, all_tools=None),
+            icon=QtGui.QIcon(self.app.resource_location + "/delete32.png")
         )
 
-        self.title_box.addWidget(title_label)
+        # #############################################################################
+        # ########################## VARIABLES ########################################
+        # #############################################################################
+        self.units = ''
+        self.iso_tools = {}
+        self.tooluid = 0
 
-        # App Level label
-        self.level = QtWidgets.QLabel("")
-        self.level.setToolTip(
-            _(
-                "BASIC is suitable for a beginner. Many parameters\n"
-                "are hidden from the user in this mode.\n"
-                "ADVANCED mode will make available all parameters.\n\n"
-                "To change the application LEVEL, go to:\n"
-                "Edit -> Preferences -> General and check:\n"
-                "'APP. LEVEL' radio button."
-            )
-        )
-        self.level.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.title_box.addWidget(self.level)
+        # store here the default data for Geometry Data
+        self.default_data = {}
 
-        # Grid Layout
-        grid0 = QtWidgets.QGridLayout()
-        grid0.setColumnStretch(0, 0)
-        grid0.setColumnStretch(1, 1)
-        self.tools_box.addLayout(grid0)
+        self.obj_name = ""
+        self.grb_obj = None
 
-        self.obj_combo_label = QtWidgets.QLabel('<b>%s</b>:' % _("GERBER"))
-        self.obj_combo_label.setToolTip(
-            _("Gerber object for isolation routing.")
-        )
+        self.sel_rect = []
 
-        grid0.addWidget(self.obj_combo_label, 0, 0, 1, 2)
+        self.first_click = False
+        self.cursor_pos = None
+        self.mouse_is_dragging = False
 
-        # ################################################
-        # ##### The object to be copper cleaned ##########
-        # ################################################
-        self.object_combo = FCComboBox()
-        self.object_combo.setModel(self.app.collection)
-        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        # self.object_combo.setCurrentIndex(1)
-        self.object_combo.is_last = True
+        # store here the points for the "Polygon" area selection shape
+        self.points = []
 
-        grid0.addWidget(self.object_combo, 1, 0, 1, 2)
+        # set this as True when in middle of drawing a "Polygon" area selection shape
+        # it is made False by first click to signify that the shape is complete
+        self.poly_drawn = False
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 2, 0, 1, 2)
+        self.mm = None
+        self.mr = None
+        self.kp = None
 
-        # ### Tools ## ##
-        self.tools_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Tools Table'))
-        self.tools_table_label.setToolTip(
-            _("Tools pool from which the algorithm\n"
-              "will pick the ones used for copper clearing.")
-        )
-        grid0.addWidget(self.tools_table_label, 3, 0, 1, 2)
+        # store geometry from Polygon selection
+        self.poly_dict = {}
 
-        self.tools_table = FCTable()
-        grid0.addWidget(self.tools_table, 4, 0, 1, 2)
+        self.grid_status_memory = self.app.ui.grid_snap_btn.isChecked()
 
-        self.tools_table.setColumnCount(4)
-        # 3rd column is reserved (and hidden) for the tool ID
-        self.tools_table.setHorizontalHeaderLabels(['#', _('Diameter'), _('TT'), ''])
-        self.tools_table.setColumnHidden(3, True)
-        self.tools_table.setSortingEnabled(False)
-        # self.tools_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+        # store here the state of the combine_cb GUI element
+        # used when the rest machining is toggled
+        self.old_combine_state = None
 
-        self.tools_table.horizontalHeaderItem(0).setToolTip(
-            _("This is the Tool Number.\n"
-              "Isolation routing will start with the tool with the biggest \n"
-              "diameter, continuing until there are no more tools.\n"
-              "Only tools that create Isolation geometry will still be present\n"
-              "in the resulting geometry. This is because with some tools\n"
-              "this function will not be able to create routing geometry.")
-        )
-        self.tools_table.horizontalHeaderItem(1).setToolTip(
-            _("Tool Diameter. It's value (in current FlatCAM units)\n"
-              "is the cut width into the material."))
+        # store here solid_geometry when there are tool with isolation job
+        self.solid_geometry = []
 
-        self.tools_table.horizontalHeaderItem(2).setToolTip(
-            _("The Tool Type (TT) can be:\n"
-              "- Circular with 1 ... 4 teeth -> it is informative only. Being circular,\n"
-              "the cut width in material is exactly the tool diameter.\n"
-              "- Ball -> informative only and make reference to the Ball type endmill.\n"
-              "- V-Shape -> it will disable Z-Cut parameter in the resulting geometry UI form\n"
-              "and enable two additional UI form fields in the resulting geometry: V-Tip Dia and\n"
-              "V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such\n"
-              "as the cut width into material will be equal with the value in the Tool Diameter\n"
-              "column of this table.\n"
-              "Choosing the 'V-Shape' Tool Type automatically will select the Operation Type\n"
-              "in the resulting geometry as Isolation."))
+        self.tool_type_item_options = []
 
-        grid1 = QtWidgets.QGridLayout()
-        grid1.setColumnStretch(0, 0)
-        grid1.setColumnStretch(1, 1)
-        self.tools_box.addLayout(grid1)
+        self.grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
 
-        # Tool order
-        self.order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
-        self.order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
-                                      "'No' --> means that the used order is the one in the tool table\n"
-                                      "'Forward' --> means that the tools will be ordered from small to big\n"
-                                      "'Reverse' --> means that the tools will ordered from big to small\n\n"
-                                      "WARNING: using rest machining will automatically set the order\n"
-                                      "in reverse and disable this control."))
+        self.tooldia = None
 
-        self.order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
-                                     {'label': _('Forward'), 'value': 'fwd'},
-                                     {'label': _('Reverse'), 'value': 'rev'}])
+        # multiprocessing
+        self.pool = self.app.pool
+        self.results = []
 
-        grid1.addWidget(self.order_label, 1, 0)
-        grid1.addWidget(self.order_radio, 1, 1)
+        # disconnect flags
+        self.area_sel_disconnect_flag = False
+        self.poly_sel_disconnect_flag = False
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid1.addWidget(separator_line, 2, 0, 1, 2)
+        self.form_fields = {
+            "tools_iso_passes":         self.ui.passes_entry,
+            "tools_iso_overlap":        self.ui.iso_overlap_entry,
+            "tools_iso_milling_type":   self.ui.milling_type_radio,
+            "tools_iso_combine":        self.ui.combine_passes_cb,
+            "tools_iso_follow":         self.ui.follow_cb,
+            "tools_iso_isotype":        self.ui.iso_type_radio
+        }
 
-        # #############################################################
-        # ############### Tool selection ##############################
-        # #############################################################
+        self.name2option = {
+            "i_passes":         "tools_iso_passes",
+            "i_overlap":        "tools_iso_overlap",
+            "i_milling_type":   "tools_iso_milling_type",
+            "i_combine":        "tools_iso_combine",
+            "i_follow":         "tools_iso_follow",
+            "i_iso_type":       "tools_iso_isotype"
+        }
 
-        self.grid3 = QtWidgets.QGridLayout()
-        self.grid3.setColumnStretch(0, 0)
-        self.grid3.setColumnStretch(1, 1)
-        self.tools_box.addLayout(self.grid3)
+        self.old_tool_dia = None
 
-        self.tool_sel_label = QtWidgets.QLabel('<b>%s</b>' % _("New Tool"))
-        self.grid3.addWidget(self.tool_sel_label, 1, 0, 1, 2)
+        self.connect_signals_at_init()
 
-        # Tool Type Radio Button
-        self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
-        self.tool_type_label.setToolTip(
-            _("Default tool type:\n"
-              "- 'V-shape'\n"
-              "- Circular")
-        )
+    def install(self, icon=None, separator=None, **kwargs):
+        AppTool.install(self, icon, separator, shortcut='Alt+I', **kwargs)
 
-        self.tool_type_radio = RadioSet([{'label': _('V-shape'), 'value': 'V'},
-                                         {'label': _('Circular'), 'value': 'C1'}])
-        self.tool_type_radio.setToolTip(
-            _("Default tool type:\n"
-              "- 'V-shape'\n"
-              "- Circular")
-        )
-        self.tool_type_radio.setObjectName("i_tool_type")
+    def run(self, toggle=True):
+        self.app.defaults.report_usage("ToolIsolation()")
+        log.debug("ToolIsolation().run() was launched ...")
 
-        self.grid3.addWidget(self.tool_type_label, 2, 0)
-        self.grid3.addWidget(self.tool_type_radio, 2, 1)
+        if toggle:
+            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+            else:
+                try:
+                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
+                except AttributeError:
+                    pass
+        else:
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
 
-        # Tip Dia
-        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
-        self.tipdialabel.setToolTip(
-            _("The tip diameter for V-Shape Tool"))
-        self.tipdia_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.tipdia_entry.set_precision(self.decimals)
-        self.tipdia_entry.set_range(0.0000, 9999.9999)
-        self.tipdia_entry.setSingleStep(0.1)
-        self.tipdia_entry.setObjectName("i_vtipdia")
+        AppTool.run(self)
+        self.set_tool_ui()
 
-        self.grid3.addWidget(self.tipdialabel, 3, 0)
-        self.grid3.addWidget(self.tipdia_entry, 3, 1)
+        # reset those objects on a new run
+        self.grb_obj = None
+        self.obj_name = ''
 
-        # Tip Angle
-        self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
-        self.tipanglelabel.setToolTip(
-            _("The tip angle for V-Shape Tool.\n"
-              "In degree."))
-        self.tipangle_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.tipangle_entry.set_precision(self.decimals)
-        self.tipangle_entry.set_range(0.0000, 180.0000)
-        self.tipangle_entry.setSingleStep(5)
-        self.tipangle_entry.setObjectName("i_vtipangle")
+        self.build_ui()
 
-        self.grid3.addWidget(self.tipanglelabel, 4, 0)
-        self.grid3.addWidget(self.tipangle_entry, 4, 1)
+        # all the tools are selected by default
+        self.ui.tools_table.selectAll()
 
-        # Cut Z entry
-        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
-        cutzlabel.setToolTip(
-            _("Depth of cut into material. Negative value.\n"
-              "In FlatCAM units.")
-        )
-        self.cutz_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.cutz_entry.set_precision(self.decimals)
-        self.cutz_entry.set_range(-99999.9999, 0.0000)
-        self.cutz_entry.setObjectName("i_vcutz")
-
-        self.grid3.addWidget(cutzlabel, 5, 0)
-        self.grid3.addWidget(self.cutz_entry, 5, 1)
+        self.app.ui.notebook.setTabText(2, _("Isolation Tool"))
 
-        # ### Tool Diameter ####
-        self.addtool_entry_lbl = QtWidgets.QLabel('%s:' % _('Tool Dia'))
-        self.addtool_entry_lbl.setToolTip(
-            _("Diameter for the new tool to add in the Tool Table.\n"
-              "If the tool is V-shape type then this value is automatically\n"
-              "calculated from the other parameters.")
-        )
-        self.addtool_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.addtool_entry.set_precision(self.decimals)
-        self.addtool_entry.set_range(0.000, 9999.9999)
-        self.addtool_entry.setObjectName("i_new_tooldia")
+    def connect_signals_at_init(self):
+        # #############################################################################
+        # ############################ SIGNALS ########################################
+        # #############################################################################
+        self.ui.addtool_btn.clicked.connect(self.on_tool_add)
+        self.ui.addtool_entry.returnPressed.connect(self.on_tooldia_updated)
+        self.ui.deltool_btn.clicked.connect(self.on_tool_delete)
 
-        self.grid3.addWidget(self.addtool_entry_lbl, 6, 0)
-        self.grid3.addWidget(self.addtool_entry, 6, 1)
+        self.ui.tipdia_entry.returnPressed.connect(self.on_calculate_tooldia)
+        self.ui.tipangle_entry.returnPressed.connect(self.on_calculate_tooldia)
+        self.ui.cutz_entry.returnPressed.connect(self.on_calculate_tooldia)
 
-        bhlay = QtWidgets.QHBoxLayout()
+        self.ui.reference_combo_type.currentIndexChanged.connect(self.on_reference_combo_changed)
+        self.ui.select_combo.currentIndexChanged.connect(self.on_toggle_reference)
 
-        self.addtool_btn = QtWidgets.QPushButton(_('Add'))
-        self.addtool_btn.setToolTip(
-            _("Add a new tool to the Tool Table\n"
-              "with the diameter specified above.")
-        )
+        self.ui.rest_cb.stateChanged.connect(self.on_rest_machining_check)
+        self.ui.order_radio.activated_custom[str].connect(self.on_order_changed)
 
-        self.addtool_from_db_btn = QtWidgets.QPushButton(_('Add from DB'))
-        self.addtool_from_db_btn.setToolTip(
-            _("Add a new tool to the Tool Table\n"
-              "from the Tool Database.\n"
-              "Tool database administration in Menu: Options -> Tools Database")
-        )
+        self.ui.type_excobj_radio.activated_custom.connect(self.on_type_excobj_index_changed)
+        self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
+        self.ui.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked)
 
-        bhlay.addWidget(self.addtool_btn)
-        bhlay.addWidget(self.addtool_from_db_btn)
+        self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
+        self.ui.reset_button.clicked.connect(self.set_tool_ui)
 
-        self.grid3.addLayout(bhlay, 7, 0, 1, 2)
+        # Cleanup on Graceful exit (CTRL+ALT+X combo key)
+        self.app.cleanup.connect(self.set_tool_ui)
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.grid3.addWidget(separator_line, 8, 0, 1, 2)
+    def on_type_excobj_index_changed(self, val):
+        obj_type = 0 if val == 'gerber' else 2
+        self.ui.exc_obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.ui.exc_obj_combo.setCurrentIndex(0)
+        self.ui.exc_obj_combo.obj_type = {
+            "gerber": "Gerber", "geometry": "Geometry"
+        }[self.ui.type_excobj_radio.get_value()]
 
-        self.deltool_btn = QtWidgets.QPushButton(_('Delete'))
-        self.deltool_btn.setToolTip(
-            _("Delete a selection of tools in the Tool Table\n"
-              "by first selecting a row(s) in the Tool Table.")
-        )
-        self.grid3.addWidget(self.deltool_btn, 9, 0, 1, 2)
+    def set_tool_ui(self):
+        self.units = self.app.defaults['units'].upper()
+        self.old_tool_dia = self.app.defaults["tools_iso_newdia"]
 
-        # self.grid3.addWidget(QtWidgets.QLabel(''), 10, 0, 1, 2)
+        # try to select in the Gerber combobox the active object
+        try:
+            selected_obj = self.app.collection.get_active()
+            if selected_obj.kind == 'gerber':
+                current_name = selected_obj.options['name']
+                self.ui.object_combo.set_value(current_name)
+        except Exception:
+            pass
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.grid3.addWidget(separator_line, 11, 0, 1, 2)
+        app_mode = self.app.defaults["global_app_level"]
 
-        self.tool_data_label = QtWidgets.QLabel(
-            "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), int(1)))
-        self.tool_data_label.setToolTip(
-            _(
-                "The data used for creating GCode.\n"
-                "Each tool store it's own set of such data."
-            )
-        )
-        self.grid3.addWidget(self.tool_data_label, 12, 0, 1, 2)
+        # Show/Hide Advanced Options
+        if app_mode == 'b':
+            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
 
-        # Passes
-        passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
-        passlabel.setToolTip(
-            _("Width of the isolation gap in\n"
-              "number (integer) of tool widths.")
-        )
-        self.passes_entry = FCSpinner(callback=self.confirmation_message_int)
-        self.passes_entry.set_range(1, 999)
-        self.passes_entry.setObjectName("i_passes")
+            # override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1')
+            self.ui.tool_type_radio.set_value('C1')
+            self.ui.tool_type_label.hide()
+            self.ui.tool_type_radio.hide()
 
-        self.grid3.addWidget(passlabel, 13, 0)
-        self.grid3.addWidget(self.passes_entry, 13, 1)
+            self.ui.milling_type_label.hide()
+            self.ui.milling_type_radio.hide()
 
-        # Overlap Entry
-        overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
-        overlabel.setToolTip(
-            _("How much (percentage) of the tool width to overlap each tool pass.")
-        )
-        self.iso_overlap_entry = FCDoubleSpinner(suffix='%', callback=self.confirmation_message)
-        self.iso_overlap_entry.set_precision(self.decimals)
-        self.iso_overlap_entry.setWrapping(True)
-        self.iso_overlap_entry.set_range(0.0000, 99.9999)
-        self.iso_overlap_entry.setSingleStep(0.1)
-        self.iso_overlap_entry.setObjectName("i_overlap")
+            self.ui.iso_type_label.hide()
+            self.ui.iso_type_radio.set_value('full')
+            self.ui.iso_type_radio.hide()
 
-        self.grid3.addWidget(overlabel, 14, 0)
-        self.grid3.addWidget(self.iso_overlap_entry, 14, 1)
+            self.ui.follow_cb.set_value(False)
+            self.ui.follow_cb.hide()
+            self.ui.follow_label.hide()
 
-        # 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.ui.rest_cb.set_value(False)
+            self.ui.rest_cb.hide()
 
-        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("i_milling_type")
+            self.ui.except_cb.set_value(False)
+            self.ui.except_cb.hide()
 
-        self.grid3.addWidget(self.milling_type_label, 15, 0)
-        self.grid3.addWidget(self.milling_type_radio, 15, 1)
+            self.ui.type_excobj_radio.hide()
+            self.ui.exc_obj_combo.hide()
 
-        # Follow
-        self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
-        self.follow_label.setToolTip(
-            _("Generate a 'Follow' geometry.\n"
-              "This means that it will cut through\n"
-              "the middle of the trace.")
-        )
+            self.ui.select_combo.setCurrentIndex(0)
+            self.ui.select_combo.hide()
+            self.ui.select_label.hide()
+        else:
+            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
 
-        self.follow_cb = FCCheckBox()
-        self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
-                                    "This means that it will cut through\n"
-                                    "the middle of the trace."))
-        self.follow_cb.setObjectName("i_follow")
+            self.ui.tool_type_radio.set_value(self.app.defaults["tools_iso_tool_type"])
+            self.ui.tool_type_label.show()
+            self.ui.tool_type_radio.show()
 
-        self.grid3.addWidget(self.follow_label, 16, 0)
-        self.grid3.addWidget(self.follow_cb, 16, 1)
+            self.ui.milling_type_label.show()
+            self.ui.milling_type_radio.show()
 
-        # Isolation Type
-        self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
-        self.iso_type_label.setToolTip(
-            _("Choose how the isolation will be executed:\n"
-              "- 'Full' -> complete isolation of polygons\n"
-              "- 'Ext' -> will isolate only on the outside\n"
-              "- 'Int' -> will isolate only on the inside\n"
-              "'Exterior' isolation is almost always possible\n"
-              "(with the right tool) but 'Interior'\n"
-              "isolation can be done only when there is an opening\n"
-              "inside of the polygon (e.g polygon is a 'doughnut' shape).")
-        )
-        self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
-                                        {'label': _('Ext'), 'value': 'ext'},
-                                        {'label': _('Int'), 'value': 'int'}])
-        self.iso_type_radio.setObjectName("i_iso_type")
+            self.ui.iso_type_label.show()
+            self.ui.iso_type_radio.set_value(self.app.defaults["tools_iso_isotype"])
+            self.ui.iso_type_radio.show()
 
-        self.grid3.addWidget(self.iso_type_label, 17, 0)
-        self.grid3.addWidget(self.iso_type_radio, 17, 1)
+            self.ui.follow_cb.set_value(self.app.defaults["tools_iso_follow"])
+            self.ui.follow_cb.show()
+            self.ui.follow_label.show()
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.grid3.addWidget(separator_line, 18, 0, 1, 2)
+            self.ui.rest_cb.set_value(self.app.defaults["tools_iso_rest"])
+            self.ui.rest_cb.show()
 
-        self.apply_param_to_all = FCButton(_("Apply parameters to all tools"))
-        self.apply_param_to_all.setToolTip(
-            _("The parameters in the current form will be applied\n"
-              "on all the tools from the Tool Table.")
-        )
-        self.grid3.addWidget(self.apply_param_to_all, 22, 0, 1, 2)
+            self.ui.except_cb.set_value(self.app.defaults["tools_iso_isoexcept"])
+            self.ui.except_cb.show()
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.grid3.addWidget(separator_line, 23, 0, 1, 2)
+            self.ui.select_combo.set_value(self.app.defaults["tools_iso_selection"])
+            self.ui.select_combo.show()
+            self.ui.select_label.show()
 
-        # General Parameters
-        self.gen_param_label = QtWidgets.QLabel('<b>%s</b>' % _("Common Parameters"))
-        self.gen_param_label.setToolTip(
-            _("Parameters that are common for all tools.")
-        )
-        self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
+        if self.app.defaults["gerber_buffering"] == 'no':
+            self.ui.create_buffer_button.show()
+            try:
+                self.ui.create_buffer_button.clicked.disconnect(self.on_generate_buffer)
+            except TypeError:
+                pass
+            self.ui.create_buffer_button.clicked.connect(self.on_generate_buffer)
+        else:
+            self.ui.create_buffer_button.hide()
 
-        # Rest Machining
-        self.rest_cb = FCCheckBox('%s' % _("Rest"))
-        self.rest_cb.setObjectName("i_rest")
-        self.rest_cb.setToolTip(
-            _("If checked, use 'rest machining'.\n"
-              "Basically it will isolate outside PCB features,\n"
-              "using the biggest tool and continue with the next tools,\n"
-              "from bigger to smaller, to isolate the copper features that\n"
-              "could not be cleared by previous tool, until there is\n"
-              "no more copper features to isolate or there are no more tools.\n"
-              "If not checked, use the standard algorithm.")
-        )
+        self.ui.tools_frame.show()
 
-        self.grid3.addWidget(self.rest_cb, 25, 0)
+        self.ui.type_excobj_radio.set_value('gerber')
 
-        # Force isolation even if the interiors are not isolated
-        self.forced_rest_iso_cb = FCCheckBox(_("Forced Rest"))
-        self.forced_rest_iso_cb.setToolTip(
-            _("When checked the isolation will be done with the current tool even if\n"
-              "interiors of a polygon (holes in the polygon) could not be isolated.\n"
-              "Works when 'rest machining' is used.")
-        )
+        # run those once so the obj_type attribute is updated for the FCComboboxes
+        # so the last loaded object is displayed
+        self.on_type_excobj_index_changed(val="gerber")
+        self.on_reference_combo_changed()
 
-        self.grid3.addWidget(self.forced_rest_iso_cb, 25, 1)
+        self.ui.order_radio.set_value(self.app.defaults["tools_iso_order"])
+        self.ui.passes_entry.set_value(self.app.defaults["tools_iso_passes"])
+        self.ui.iso_overlap_entry.set_value(self.app.defaults["tools_iso_overlap"])
+        self.ui.milling_type_radio.set_value(self.app.defaults["tools_iso_milling_type"])
+        self.ui.combine_passes_cb.set_value(self.app.defaults["tools_iso_combine_passes"])
+        self.ui.area_shape_radio.set_value(self.app.defaults["tools_iso_area_shape"])
+        self.ui.poly_int_cb.set_value(self.app.defaults["tools_iso_poly_ints"])
+        self.ui.forced_rest_iso_cb.set_value(self.app.defaults["tools_iso_force"])
 
-        # Combine All Passes
-        self.combine_passes_cb = FCCheckBox(label=_('Combine'))
-        self.combine_passes_cb.setToolTip(
-            _("Combine all passes into one object")
-        )
-        self.combine_passes_cb.setObjectName("i_combine")
+        self.ui.cutz_entry.set_value(self.app.defaults["tools_iso_tool_cutz"])
+        self.ui.tool_type_radio.set_value(self.app.defaults["tools_iso_tool_type"])
+        self.ui.tipdia_entry.set_value(self.app.defaults["tools_iso_tool_vtipdia"])
+        self.ui.tipangle_entry.set_value(self.app.defaults["tools_iso_tool_vtipangle"])
+        self.ui.addtool_entry.set_value(self.app.defaults["tools_iso_newdia"])
 
-        self.grid3.addWidget(self.combine_passes_cb, 26, 0, 1, 2)
+        self.on_tool_type(val=self.ui.tool_type_radio.get_value())
 
-        # Exception Areas
-        self.except_cb = FCCheckBox(label=_('Except'))
-        self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
-                                    "by checking this, the area of the object below\n"
-                                    "will be subtracted from the isolation geometry."))
-        self.except_cb.setObjectName("i_except")
-        self.grid3.addWidget(self.except_cb, 27, 0)
+        loaded_obj = self.app.collection.get_by_name(self.ui.object_combo.get_value())
+        if loaded_obj:
+            outname = loaded_obj.options['name']
+        else:
+            outname = ''
 
-        # Type of object to be excepted
-        self.type_excobj_radio = RadioSet([{'label': _("Geometry"), 'value': 'geometry'},
-                                           {'label': _("Gerber"), 'value': 'gerber'}])
-        self.type_excobj_radio.setToolTip(
-            _("Specify the type of object to be excepted from isolation.\n"
-              "It can be of type: Gerber or Geometry.\n"
-              "What is selected here will dictate the kind\n"
-              "of objects that will populate the 'Object' combobox.")
-        )
+        # init the working variables
+        self.default_data.clear()
+        self.default_data = {
+            "name":                     outname + '_iso',
+            "plot":                     self.app.defaults["geometry_plot"],
+            "cutz":                     float(self.app.defaults["tools_iso_tool_cutz"]),
+            "vtipdia":                  float(self.app.defaults["tools_iso_tool_vtipdia"]),
+            "vtipangle":                float(self.app.defaults["tools_iso_tool_vtipangle"]),
+            "travelz":                  self.app.defaults["geometry_travelz"],
+            "feedrate":                 self.app.defaults["geometry_feedrate"],
+            "feedrate_z":               self.app.defaults["geometry_feedrate_z"],
+            "feedrate_rapid":           self.app.defaults["geometry_feedrate_rapid"],
+            "dwell":                    self.app.defaults["geometry_dwell"],
+            "dwelltime":                self.app.defaults["geometry_dwelltime"],
+            "multidepth":               self.app.defaults["geometry_multidepth"],
+            "ppname_g":                 self.app.defaults["geometry_ppname_g"],
+            "depthperpass":             self.app.defaults["geometry_depthperpass"],
+            "extracut":                 self.app.defaults["geometry_extracut"],
+            "extracut_length":          self.app.defaults["geometry_extracut_length"],
+            "toolchange":               self.app.defaults["geometry_toolchange"],
+            "toolchangez":              self.app.defaults["geometry_toolchangez"],
+            "endz":                     self.app.defaults["geometry_endz"],
+            "endxy":                    self.app.defaults["geometry_endxy"],
 
-        self.grid3.addWidget(self.type_excobj_radio, 27, 1)
+            "spindlespeed":             self.app.defaults["geometry_spindlespeed"],
+            "toolchangexy":             self.app.defaults["geometry_toolchangexy"],
+            "startz":                   self.app.defaults["geometry_startz"],
 
-        # The object to be excepted
-        self.exc_obj_combo = FCComboBox()
-        self.exc_obj_combo.setToolTip(_("Object whose area will be removed from isolation geometry."))
+            "area_exclusion":           self.app.defaults["geometry_area_exclusion"],
+            "area_shape":               self.app.defaults["geometry_area_shape"],
+            "area_strategy":            self.app.defaults["geometry_area_strategy"],
+            "area_overz":               float(self.app.defaults["geometry_area_overz"]),
 
-        # set the model for the Area Exception comboboxes
-        self.exc_obj_combo.setModel(self.app.collection)
-        self.exc_obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.exc_obj_combo.is_last = True
-        self.exc_obj_combo.obj_type = self.type_excobj_radio.get_value()
+            "tools_iso_passes":         self.app.defaults["tools_iso_passes"],
+            "tools_iso_overlap":        self.app.defaults["tools_iso_overlap"],
+            "tools_iso_milling_type":   self.app.defaults["tools_iso_milling_type"],
+            "tools_iso_follow":         self.app.defaults["tools_iso_follow"],
+            "tools_iso_isotype":        self.app.defaults["tools_iso_isotype"],
 
-        self.grid3.addWidget(self.exc_obj_combo, 29, 0, 1, 2)
+            "tools_iso_rest":           self.app.defaults["tools_iso_rest"],
+            "tools_iso_combine_passes": self.app.defaults["tools_iso_combine_passes"],
+            "tools_iso_isoexcept":      self.app.defaults["tools_iso_isoexcept"],
+            "tools_iso_selection":      self.app.defaults["tools_iso_selection"],
+            "tools_iso_poly_ints":      self.app.defaults["tools_iso_poly_ints"],
+            "tools_iso_force":          self.app.defaults["tools_iso_force"],
+            "tools_iso_area_shape":     self.app.defaults["tools_iso_area_shape"]
+        }
 
-        self.e_ois = OptionalInputSection(self.except_cb,
-                                          [
-                                              self.type_excobj_radio,
-                                              self.exc_obj_combo
-                                          ])
+        try:
+            dias = [float(self.app.defaults["tools_iso_tooldia"])]
+        except (ValueError, TypeError):
+            dias = [float(eval(dia)) for dia in self.app.defaults["tools_iso_tooldia"].split(",") if dia != '']
 
-        # Isolation Scope
-        self.select_label = QtWidgets.QLabel('%s:' % _("Selection"))
-        self.select_label.setToolTip(
-            _("Isolation scope. Choose what to isolate:\n"
-              "- 'All' -> Isolate all the polygons in the object\n"
-              "- 'Area Selection' -> Isolate polygons within a selection area.\n"
-              "- 'Polygon Selection' -> Isolate a selection of polygons.\n"
-              "- 'Reference Object' - will process the area specified by another object.")
-        )
-        self.select_combo = FCComboBox()
-        self.select_combo.addItems(
-            [_("All"), _("Area Selection"), _("Polygon Selection"), _("Reference Object")]
-        )
-        self.select_combo.setObjectName("i_selection")
+        if not dias:
+            log.error("At least one tool diameter needed. Verify in Edit -> Preferences -> TOOLS -> Isolation Tools.")
+            return
 
-        self.grid3.addWidget(self.select_label, 30, 0)
-        self.grid3.addWidget(self.select_combo, 30, 1)
+        self.tooluid = 0
 
-        self.reference_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
-        self.reference_combo_type_label.setToolTip(
-            _("The type of FlatCAM object to be used as non copper clearing reference.\n"
-              "It can be Gerber, Excellon or Geometry.")
-        )
-        self.reference_combo_type = FCComboBox()
-        self.reference_combo_type.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
+        self.iso_tools.clear()
+        for tool_dia in dias:
+            self.tooluid += 1
+            self.iso_tools.update({
+                int(self.tooluid): {
+                    'tooldia':          float('%.*f' % (self.decimals, tool_dia)),
+                    'offset':           'Path',
+                    'offset_value':     0.0,
+                    'type':             'Iso',
+                    'tool_type':        self.ui.tool_type_radio.get_value(),
+                    'data':             deepcopy(self.default_data),
+                    'solid_geometry':   []
+                }
+            })
 
-        self.grid3.addWidget(self.reference_combo_type_label, 31, 0)
-        self.grid3.addWidget(self.reference_combo_type, 31, 1)
+        self.obj_name = ""
+        self.grb_obj = None
 
-        self.reference_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
-        self.reference_combo_label.setToolTip(
-            _("The FlatCAM object to be used as non copper clearing reference.")
-        )
-        self.reference_combo = FCComboBox()
-        self.reference_combo.setModel(self.app.collection)
-        self.reference_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.reference_combo.is_last = True
+        self.first_click = False
+        self.cursor_pos = None
+        self.mouse_is_dragging = False
 
-        self.grid3.addWidget(self.reference_combo_label, 32, 0)
-        self.grid3.addWidget(self.reference_combo, 32, 1)
+        prog_plot = True if self.app.defaults["tools_iso_plotting"] == 'progressive' else False
+        if prog_plot:
+            self.temp_shapes.clear(update=True)
 
-        self.reference_combo.hide()
-        self.reference_combo_label.hide()
-        self.reference_combo_type.hide()
-        self.reference_combo_type_label.hide()
+        self.sel_rect = []
 
-        # Polygon interiors selection
-        self.poly_int_cb = FCCheckBox(_("Interiors"))
-        self.poly_int_cb.setToolTip(
-            _("When checked the user can select interiors of a polygon.\n"
-              "(holes in the polygon).")
-        )
+        self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
+        self.units = self.app.defaults['units'].upper()
 
-        self.grid3.addWidget(self.poly_int_cb, 33, 0)
+        self.ui.tools_table.drag_drop_sig.connect(self.rebuild_ui)
 
-        self.poly_int_cb.hide()
+    def rebuild_ui(self):
+        # read the table tools uid
+        current_uid_list = []
+        for row in range(self.ui.tools_table.rowCount()):
+            uid = int(self.ui.tools_table.item(row, 3).text())
+            current_uid_list.append(uid)
 
-        # Area Selection shape
-        self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
-        self.area_shape_label.setToolTip(
-            _("The kind of selection shape used for area selection.")
-        )
+        new_tools = {}
+        new_uid = 1
 
-        self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
-                                          {'label': _("Polygon"), 'value': 'polygon'}])
+        for current_uid in current_uid_list:
+            new_tools[new_uid] = deepcopy(self.iso_tools[current_uid])
+            new_uid += 1
 
-        self.grid3.addWidget(self.area_shape_label, 35, 0)
-        self.grid3.addWidget(self.area_shape_radio, 35, 1)
+        self.iso_tools = new_tools
 
-        self.area_shape_label.hide()
-        self.area_shape_radio.hide()
+        # the tools table changed therefore we need to rebuild it
+        QtCore.QTimer.singleShot(20, self.build_ui)
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.grid3.addWidget(separator_line, 36, 0, 1, 2)
+    def build_ui(self):
+        self.ui_disconnect()
 
-        self.generate_iso_button = QtWidgets.QPushButton("%s" % _("Generate Isolation Geometry"))
-        self.generate_iso_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.generate_iso_button.setToolTip(
-            _("Create a Geometry object with toolpaths to cut \n"
-              "isolation outside, inside or on both sides of the\n"
-              "object. For a Gerber object outside means outside\n"
-              "of the Gerber feature and inside means inside of\n"
-              "the Gerber feature, if possible at all. This means\n"
-              "that only if the Gerber feature has openings inside, they\n"
-              "will be isolated. If what is wanted is to cut isolation\n"
-              "inside the actual Gerber feature, use a negative tool\n"
-              "diameter above.")
-        )
-        self.tools_box.addWidget(self.generate_iso_button)
+        # updated units
+        self.units = self.app.defaults['units'].upper()
 
-        self.create_buffer_button = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
-        self.create_buffer_button.setToolTip(
-            _("This button is shown only when the Gerber file\n"
-              "is loaded without buffering.\n"
-              "Clicking this will create the buffered geometry\n"
-              "required for isolation.")
-        )
-        self.tools_box.addWidget(self.create_buffer_button)
+        sorted_tools = []
+        for k, v in self.iso_tools.items():
+            sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
 
-        self.tools_box.addStretch()
+        order = self.ui.order_radio.get_value()
+        if order == 'fwd':
+            sorted_tools.sort(reverse=False)
+        elif order == 'rev':
+            sorted_tools.sort(reverse=True)
+        else:
+            pass
 
-        # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
-        self.reset_button.setToolTip(
-            _("Will reset the tool parameters.")
-        )
-        self.reset_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.tools_box.addWidget(self.reset_button)
-        # ############################ FINSIHED GUI ###################################
-        # #############################################################################
+        n = len(sorted_tools)
+        self.ui.tools_table.setRowCount(n)
+        tool_id = 0
 
-        # #############################################################################
-        # ###################### Setup CONTEXT MENU ###################################
-        # #############################################################################
-        self.tools_table.setupContextMenu()
-        self.tools_table.addContextMenu(
-            _("Add"), self.on_add_tool_by_key, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")
-        )
-        self.tools_table.addContextMenu(
-            _("Add from DB"), self.on_add_tool_by_key, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")
-        )
-        self.tools_table.addContextMenu(
-            _("Delete"), lambda:
-            self.on_tool_delete(rows_to_delete=None, all_tools=None),
-            icon=QtGui.QIcon(self.app.resource_location + "/delete32.png")
-        )
+        for tool_sorted in sorted_tools:
+            for tooluid_key, tooluid_value in self.iso_tools.items():
+                if float('%.*f' % (self.decimals, tooluid_value['tooldia'])) == tool_sorted:
+                    tool_id += 1
+                    id_ = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
+                    id_.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+                    row_no = tool_id - 1
+                    self.ui.tools_table.setItem(row_no, 0, id_)  # Tool name/id
 
-        # #############################################################################
-        # ########################## VARIABLES ########################################
-        # #############################################################################
-        self.units = ''
-        self.iso_tools = {}
-        self.tooluid = 0
 
-        # store here the default data for Geometry Data
-        self.default_data = {}
+                    dia = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, tooluid_value['tooldia']))
+                    dia.setFlags(QtCore.Qt.ItemIsEnabled)
+                    self.ui.tools_table.setItem(row_no, 1, dia)  # Diameter
 
-        self.obj_name = ""
-        self.grb_obj = None
+                    tool_type_item = FCComboBox()
+                    tool_type_item.addItems(self.tool_type_item_options)
+                    # tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
+                    idx = tool_type_item.findText(tooluid_value['tool_type'])
+                    tool_type_item.setCurrentIndex(idx)
+                    self.ui.tools_table.setCellWidget(row_no, 2, tool_type_item)
 
-        self.sel_rect = []
+                    tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
+                    # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
+                    self.ui.tools_table.setItem(row_no, 3, tool_uid_item)  # Tool unique ID
 
-        self.first_click = False
-        self.cursor_pos = None
-        self.mouse_is_dragging = False
+        # make the diameter column editable
+        for row in range(tool_id):
+            self.ui.tools_table.item(row, 1).setFlags(
+                QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 
-        # store here the points for the "Polygon" area selection shape
-        self.points = []
+        # all the tools are selected by default
+        self.ui.tools_table.selectColumn(0)
+        #
+        self.ui.tools_table.resizeColumnsToContents()
+        self.ui.tools_table.resizeRowsToContents()
 
-        # set this as True when in middle of drawing a "Polygon" area selection shape
-        # it is made False by first click to signify that the shape is complete
-        self.poly_drawn = False
+        vertical_header = self.ui.tools_table.verticalHeader()
+        vertical_header.hide()
+        self.ui.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
 
-        self.mm = None
-        self.mr = None
-        self.kp = None
+        horizontal_header = self.ui.tools_table.horizontalHeader()
+        horizontal_header.setMinimumSectionSize(10)
+        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(0, 20)
+        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
 
-        # store geometry from Polygon selection
-        self.poly_dict = {}
+        # self.ui.tools_table.setSortingEnabled(True)
+        # sort by tool diameter
+        # self.ui.tools_table.sortItems(1)
 
-        self.grid_status_memory = self.app.ui.grid_snap_btn.isChecked()
+        self.ui.tools_table.setMinimumHeight(self.ui.tools_table.getHeight())
+        self.ui.tools_table.setMaximumHeight(self.ui.tools_table.getHeight())
 
-        # store here the state of the combine_cb GUI element
-        # used when the rest machining is toggled
-        self.old_combine_state = None
+        self.ui_connect()
 
-        # store here solid_geometry when there are tool with isolation job
-        self.solid_geometry = []
+        # set the text on tool_data_label after loading the object
+        sel_rows = set()
+        sel_items = self.ui.tools_table.selectedItems()
+        for it in sel_items:
+            sel_rows.add(it.row())
+        if len(sel_rows) > 1:
+            self.ui.tool_data_label.setText(
+                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
+            )
 
-        self.tool_type_item_options = []
+    def ui_connect(self):
+        self.ui.tools_table.itemChanged.connect(self.on_tool_edit)
 
-        self.grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
+        # rows selected
+        self.ui.tools_table.clicked.connect(self.on_row_selection_change)
+        self.ui.tools_table.horizontalHeader().sectionClicked.connect(self.on_toggle_all_rows)
 
-        self.tooldia = None
+        for row in range(self.ui.tools_table.rowCount()):
+            try:
+                self.ui.tools_table.cellWidget(row, 2).currentIndexChanged.connect(self.on_tooltable_cellwidget_change)
+            except AttributeError:
+                pass
 
-        # multiprocessing
-        self.pool = self.app.pool
-        self.results = []
-
-        # disconnect flags
-        self.area_sel_disconnect_flag = False
-        self.poly_sel_disconnect_flag = False
+        self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type)
 
-        self.form_fields = {
-            "tools_iso_passes":         self.passes_entry,
-            "tools_iso_overlap":        self.iso_overlap_entry,
-            "tools_iso_milling_type":   self.milling_type_radio,
-            "tools_iso_combine":        self.combine_passes_cb,
-            "tools_iso_follow":         self.follow_cb,
-            "tools_iso_isotype":        self.iso_type_radio
-        }
+        for opt in self.form_fields:
+            current_widget = self.form_fields[opt]
+            if isinstance(current_widget, FCCheckBox):
+                current_widget.stateChanged.connect(self.form_to_storage)
+            if isinstance(current_widget, RadioSet):
+                current_widget.activated_custom.connect(self.form_to_storage)
+            elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner):
+                current_widget.returnPressed.connect(self.form_to_storage)
+            elif isinstance(current_widget, FCComboBox):
+                current_widget.currentIndexChanged.connect(self.form_to_storage)
 
-        self.name2option = {
-            "i_passes":         "tools_iso_passes",
-            "i_overlap":        "tools_iso_overlap",
-            "i_milling_type":   "tools_iso_milling_type",
-            "i_combine":        "tools_iso_combine",
-            "i_follow":         "tools_iso_follow",
-            "i_iso_type":       "tools_iso_isotype"
-        }
+        self.ui.rest_cb.stateChanged.connect(self.on_rest_machining_check)
+        self.ui.order_radio.activated_custom[str].connect(self.on_order_changed)
 
-        self.old_tool_dia = None
+    def ui_disconnect(self):
 
-        # #############################################################################
-        # ############################ SIGNALS ########################################
-        # #############################################################################
-        self.addtool_btn.clicked.connect(self.on_tool_add)
-        self.addtool_entry.returnPressed.connect(self.on_tooldia_updated)
-        self.deltool_btn.clicked.connect(self.on_tool_delete)
+        try:
+            # if connected, disconnect the signal from the slot on item_changed as it creates issues
+            self.ui.tools_table.itemChanged.disconnect()
+        except (TypeError, AttributeError):
+            pass
 
-        self.tipdia_entry.returnPressed.connect(self.on_calculate_tooldia)
-        self.tipangle_entry.returnPressed.connect(self.on_calculate_tooldia)
-        self.cutz_entry.returnPressed.connect(self.on_calculate_tooldia)
+        try:
+            # if connected, disconnect the signal from the slot on item_changed as it creates issues
+            self.ui.tool_type_radio.activated_custom.disconnect()
+        except (TypeError, AttributeError):
+            pass
 
-        self.reference_combo_type.currentIndexChanged.connect(self.on_reference_combo_changed)
-        self.select_combo.currentIndexChanged.connect(self.on_toggle_reference)
+        for row in range(self.ui.tools_table.rowCount()):
 
-        self.rest_cb.stateChanged.connect(self.on_rest_machining_check)
-        self.order_radio.activated_custom[str].connect(self.on_order_changed)
+            try:
+                self.ui.tools_table.cellWidget(row, 2).currentIndexChanged.disconnect()
+            except (TypeError, AttributeError):
+                pass
 
-        self.type_excobj_radio.activated_custom.connect(self.on_type_excobj_index_changed)
-        self.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
-        self.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked)
+        for opt in self.form_fields:
+            current_widget = self.form_fields[opt]
+            if isinstance(current_widget, FCCheckBox):
+                try:
+                    current_widget.stateChanged.disconnect(self.form_to_storage)
+                except (TypeError, ValueError):
+                    pass
+            if isinstance(current_widget, RadioSet):
+                try:
+                    current_widget.activated_custom.disconnect(self.form_to_storage)
+                except (TypeError, ValueError):
+                    pass
+            elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner):
+                try:
+                    current_widget.returnPressed.disconnect(self.form_to_storage)
+                except (TypeError, ValueError):
+                    pass
+            elif isinstance(current_widget, FCComboBox):
+                try:
+                    current_widget.currentIndexChanged.disconnect(self.form_to_storage)
+                except (TypeError, ValueError):
+                    pass
 
-        self.generate_iso_button.clicked.connect(self.on_iso_button_click)
-        self.reset_button.clicked.connect(self.set_tool_ui)
+        try:
+            self.ui.rest_cb.stateChanged.disconnect()
+        except (TypeError, ValueError):
+            pass
+        try:
+            self.ui.order_radio.activated_custom[str].disconnect()
+        except (TypeError, ValueError):
+            pass
 
-        # Cleanup on Graceful exit (CTRL+ALT+X combo key)
-        self.app.cleanup.connect(self.reset_usage)
+        # rows selected
+        try:
+            self.ui.tools_table.clicked.disconnect()
+        except (TypeError, AttributeError):
+            pass
+        try:
+            self.ui.tools_table.horizontalHeader().sectionClicked.disconnect()
+        except (TypeError, AttributeError):
+            pass
 
-    def on_type_excobj_index_changed(self, val):
-        obj_type = 0 if val == 'gerber' else 2
-        self.exc_obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.exc_obj_combo.setCurrentIndex(0)
-        self.exc_obj_combo.obj_type = {
-            "gerber": "Gerber", "geometry": "Geometry"
-        }[self.type_excobj_radio.get_value()]
+    def on_toggle_all_rows(self):
+        """
+        will toggle the selection of all rows in Tools table
 
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Alt+I', **kwargs)
+        :return:
+        """
+        sel_model = self.ui.tools_table.selectionModel()
+        sel_indexes = sel_model.selectedIndexes()
 
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolIsolation()")
-        log.debug("ToolIsolation().run() was launched ...")
+        # it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
+        sel_rows = set()
+        for idx in sel_indexes:
+            sel_rows.add(idx.row())
 
-        if toggle:
-            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
-            else:
-                try:
-                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        # if tab is populated with the tool but it does not have the focus, focus on it
-                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
-                            # focus on Tool Tab
-                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
-                        else:
-                            self.app.ui.splitter.setSizes([0, 1])
-                except AttributeError:
-                    pass
+        if len(sel_rows) == self.ui.tools_table.rowCount():
+            self.ui.tools_table.clearSelection()
         else:
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
+            self.ui.tools_table.selectAll()
+        self.update_ui()
 
-        AppTool.run(self)
-        self.set_tool_ui()
+    def on_row_selection_change(self):
+        self.update_ui()
 
-        # reset those objects on a new run
-        self.grb_obj = None
-        self.obj_name = ''
+    def update_ui(self):
+        self.blockSignals(True)
 
-        self.build_ui()
-        self.app.ui.notebook.setTabText(2, _("Isolation Tool"))
+        sel_rows = set()
+        table_items = self.ui.tools_table.selectedItems()
+        if table_items:
+            for it in table_items:
+                sel_rows.add(it.row())
+            # sel_rows = sorted(set(index.row() for index in self.ui.tools_table.selectedIndexes()))
+        else:
+            sel_rows = [0]
 
-    def set_tool_ui(self):
-        self.units = self.app.defaults['units'].upper()
-        self.old_tool_dia = self.app.defaults["tools_iso_newdia"]
+        if not sel_rows:
+            return
 
-        # try to select in the Gerber combobox the active object
-        try:
-            selected_obj = self.app.collection.get_active()
-            if selected_obj.kind == 'gerber':
-                current_name = selected_obj.options['name']
-                self.object_combo.set_value(current_name)
-        except Exception:
-            pass
+        for current_row in sel_rows:
+            # populate the form with the data from the tool associated with the row parameter
+            try:
+                item = self.ui.tools_table.item(current_row, 3)
+                if item is not None:
+                    tooluid = int(item.text())
+                else:
+                    return
+            except Exception as e:
+                log.debug("Tool missing. Add a tool in the Tool Table. %s" % str(e))
+                return
 
-        app_mode = self.app.defaults["global_app_level"]
+            # update the QLabel that shows for which Tool we have the parameters in the UI form
+            if len(sel_rows) == 1:
+                cr = current_row + 1
+                self.ui.tool_data_label.setText(
+                    "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), cr)
+                )
+                try:
+                    # set the form with data from the newly selected tool
+                    for tooluid_key, tooluid_value in list(self.iso_tools.items()):
+                        if int(tooluid_key) == tooluid:
+                            for key, value in tooluid_value.items():
+                                if key == 'data':
+                                    self.storage_to_form(tooluid_value['data'])
+                except Exception as e:
+                    log.debug("ToolIsolation ---> update_ui() " + str(e))
+            else:
+                self.ui.tool_data_label.setText(
+                    "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
+                )
 
-        # Show/Hide Advanced Options
-        if app_mode == 'b':
-            self.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
+        self.blockSignals(False)
 
-            # override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1')
-            self.tool_type_radio.set_value('C1')
-            self.tool_type_label.hide()
-            self.tool_type_radio.hide()
+    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:
+                        log.debug("ToolIsolation.storage_to_form() --> %s" % str(e))
+                        pass
 
-            self.milling_type_label.hide()
-            self.milling_type_radio.hide()
+    def form_to_storage(self):
+        if self.ui.tools_table.rowCount() == 0:
+            # there is no tool in tool table so we can't save the GUI elements values to storage
+            return
 
-            self.iso_type_label.hide()
-            self.iso_type_radio.set_value('full')
-            self.iso_type_radio.hide()
+        self.blockSignals(True)
 
-            self.follow_cb.set_value(False)
-            self.follow_cb.hide()
-            self.follow_label.hide()
+        widget_changed = self.sender()
+        wdg_objname = widget_changed.objectName()
+        option_changed = self.name2option[wdg_objname]
 
-            self.rest_cb.set_value(False)
-            self.rest_cb.hide()
+        # row = self.ui.tools_table.currentRow()
+        rows = sorted(set(index.row() for index in self.ui.tools_table.selectedIndexes()))
+        for row in rows:
+            if row < 0:
+                row = 0
+            tooluid_item = int(self.ui.tools_table.item(row, 3).text())
 
-            self.except_cb.set_value(False)
-            self.except_cb.hide()
+            for tooluid_key, tooluid_val in self.iso_tools.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.type_excobj_radio.hide()
-            self.exc_obj_combo.hide()
+        self.blockSignals(False)
 
-            self.select_combo.setCurrentIndex(0)
-            self.select_combo.hide()
-            self.select_label.hide()
-        else:
-            self.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
+    def on_apply_param_to_all_clicked(self):
+        if self.ui.tools_table.rowCount() == 0:
+            # there is no tool in tool table so we can't save the GUI elements values to storage
+            log.debug("ToolIsolation.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.")
+            return
 
-            self.tool_type_radio.set_value(self.app.defaults["tools_iso_tool_type"])
-            self.tool_type_label.show()
-            self.tool_type_radio.show()
+        self.blockSignals(True)
 
-            self.milling_type_label.show()
-            self.milling_type_radio.show()
+        row = self.ui.tools_table.currentRow()
+        if row < 0:
+            row = 0
 
-            self.iso_type_label.show()
-            self.iso_type_radio.set_value(self.app.defaults["tools_iso_isotype"])
-            self.iso_type_radio.show()
+        tooluid_item = int(self.ui.tools_table.item(row, 3).text())
+        temp_tool_data = {}
 
-            self.follow_cb.set_value(self.app.defaults["tools_iso_follow"])
-            self.follow_cb.show()
-            self.follow_label.show()
+        for tooluid_key, tooluid_val in self.iso_tools.items():
+            if int(tooluid_key) == tooluid_item:
+                # this will hold the 'data' key of the self.tools[tool] dictionary that corresponds to
+                # the current row in the tool table
+                temp_tool_data = tooluid_val['data']
+                break
 
-            self.rest_cb.set_value(self.app.defaults["tools_iso_rest"])
-            self.rest_cb.show()
+        for tooluid_key, tooluid_val in self.iso_tools.items():
+            tooluid_val['data'] = deepcopy(temp_tool_data)
 
-            self.except_cb.set_value(self.app.defaults["tools_iso_isoexcept"])
-            self.except_cb.show()
+        self.app.inform.emit('[success] %s' % _("Current Tool parameters were applied to all tools."))
+        self.blockSignals(False)
 
-            self.select_combo.set_value(self.app.defaults["tools_iso_selection"])
-            self.select_combo.show()
-            self.select_label.show()
+    def on_add_tool_by_key(self):
+        tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
+                                       text='%s:' % _('Enter a Tool Diameter'),
+                                       min=0.0001, max=9999.9999, decimals=self.decimals)
+        tool_add_popup.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/letter_t_32.png'))
 
-        if self.app.defaults["gerber_buffering"] == 'no':
-            self.create_buffer_button.show()
-            try:
-                self.create_buffer_button.clicked.disconnect(self.on_generate_buffer)
-            except TypeError:
-                pass
-            self.create_buffer_button.clicked.connect(self.on_generate_buffer)
+        val, ok = tool_add_popup.get_value()
+        if ok:
+            if float(val) == 0:
+                self.app.inform.emit('[WARNING_NOTCL] %s' %
+                                     _("Please enter a tool diameter with non-zero value, in Float format."))
+                return
+            self.on_tool_add(dia=float(val))
         else:
-            self.create_buffer_button.hide()
+            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Adding Tool cancelled"))
+
+    def on_tooldia_updated(self):
+        if self.ui.tool_type_radio.get_value() == 'C1':
+            self.old_tool_dia = self.ui.addtool_entry.get_value()
 
-        self.tools_frame.show()
+    def on_reference_combo_changed(self):
+        obj_type = self.ui.reference_combo_type.currentIndex()
+        self.ui.reference_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.ui.reference_combo.setCurrentIndex(0)
+        self.ui.reference_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.ui.reference_combo_type.get_value()]
 
-        self.type_excobj_radio.set_value('gerber')
+    def on_toggle_reference(self):
+        val = self.ui.select_combo.get_value()
 
-        # run those once so the obj_type attribute is updated for the FCComboboxes
-        # so the last loaded object is displayed
-        self.on_type_excobj_index_changed(val="gerber")
-        self.on_reference_combo_changed()
+        if val == _("All"):
+            self.ui.reference_combo.hide()
+            self.ui.reference_combo_label.hide()
+            self.ui.reference_combo_type.hide()
+            self.ui.reference_combo_type_label.hide()
+            self.ui.area_shape_label.hide()
+            self.ui.area_shape_radio.hide()
+            self.ui.poly_int_cb.hide()
 
-        self.order_radio.set_value(self.app.defaults["tools_iso_order"])
-        self.passes_entry.set_value(self.app.defaults["tools_iso_passes"])
-        self.iso_overlap_entry.set_value(self.app.defaults["tools_iso_overlap"])
-        self.milling_type_radio.set_value(self.app.defaults["tools_iso_milling_type"])
-        self.combine_passes_cb.set_value(self.app.defaults["tools_iso_combine_passes"])
-        self.area_shape_radio.set_value(self.app.defaults["tools_iso_area_shape"])
-        self.poly_int_cb.set_value(self.app.defaults["tools_iso_poly_ints"])
-        self.forced_rest_iso_cb.set_value(self.app.defaults["tools_iso_force"])
+            # disable rest-machining for area painting
+            self.ui.rest_cb.setDisabled(False)
+        elif val == _("Area Selection"):
+            self.ui.reference_combo.hide()
+            self.ui.reference_combo_label.hide()
+            self.ui.reference_combo_type.hide()
+            self.ui.reference_combo_type_label.hide()
+            self.ui.area_shape_label.show()
+            self.ui.area_shape_radio.show()
+            self.ui.poly_int_cb.hide()
 
-        self.cutz_entry.set_value(self.app.defaults["tools_iso_tool_cutz"])
-        self.tool_type_radio.set_value(self.app.defaults["tools_iso_tool_type"])
-        self.tipdia_entry.set_value(self.app.defaults["tools_iso_tool_vtipdia"])
-        self.tipangle_entry.set_value(self.app.defaults["tools_iso_tool_vtipangle"])
-        self.addtool_entry.set_value(self.app.defaults["tools_iso_newdia"])
+            # disable rest-machining for area isolation
+            self.ui.rest_cb.set_value(False)
+            self.ui.rest_cb.setDisabled(True)
+        elif val == _("Polygon Selection"):
+            self.ui.reference_combo.hide()
+            self.ui.reference_combo_label.hide()
+            self.ui.reference_combo_type.hide()
+            self.ui.reference_combo_type_label.hide()
+            self.ui.area_shape_label.hide()
+            self.ui.area_shape_radio.hide()
+            self.ui.poly_int_cb.show()
+        else:
+            self.ui.reference_combo.show()
+            self.ui.reference_combo_label.show()
+            self.ui.reference_combo_type.show()
+            self.ui.reference_combo_type_label.show()
+            self.ui.area_shape_label.hide()
+            self.ui.area_shape_radio.hide()
+            self.ui.poly_int_cb.hide()
 
-        self.on_tool_type(val=self.tool_type_radio.get_value())
+            # disable rest-machining for area painting
+            self.ui.rest_cb.setDisabled(False)
 
-        loaded_obj = self.app.collection.get_by_name(self.object_combo.get_value())
-        if loaded_obj:
-            outname = loaded_obj.options['name']
+    def on_order_changed(self, order):
+        if order != 'no':
+            self.build_ui()
+
+    def on_rest_machining_check(self, state):
+        if state:
+            self.ui.order_radio.set_value('rev')
+            self.ui.order_label.setDisabled(True)
+            self.ui.order_radio.setDisabled(True)
+
+            self.old_combine_state = self.ui.combine_passes_cb.get_value()
+            self.ui.combine_passes_cb.set_value(True)
+            self.ui.combine_passes_cb.setDisabled(True)
+
+            self.ui.forced_rest_iso_cb.setDisabled(False)
         else:
-            outname = ''
+            self.ui.order_label.setDisabled(False)
+            self.ui.order_radio.setDisabled(False)
 
-        # init the working variables
-        self.default_data.clear()
-        self.default_data = {
-            "name":                     outname + '_iso',
-            "plot":                     self.app.defaults["geometry_plot"],
-            "cutz":                     float(self.cutz_entry.get_value()),
-            "vtipdia":                  float(self.tipdia_entry.get_value()),
-            "vtipangle":                float(self.tipangle_entry.get_value()),
-            "travelz":                  self.app.defaults["geometry_travelz"],
-            "feedrate":                 self.app.defaults["geometry_feedrate"],
-            "feedrate_z":               self.app.defaults["geometry_feedrate_z"],
-            "feedrate_rapid":           self.app.defaults["geometry_feedrate_rapid"],
-            "dwell":                    self.app.defaults["geometry_dwell"],
-            "dwelltime":                self.app.defaults["geometry_dwelltime"],
-            "multidepth":               self.app.defaults["geometry_multidepth"],
-            "ppname_g":                 self.app.defaults["geometry_ppname_g"],
-            "depthperpass":             self.app.defaults["geometry_depthperpass"],
-            "extracut":                 self.app.defaults["geometry_extracut"],
-            "extracut_length":          self.app.defaults["geometry_extracut_length"],
-            "toolchange":               self.app.defaults["geometry_toolchange"],
-            "toolchangez":              self.app.defaults["geometry_toolchangez"],
-            "endz":                     self.app.defaults["geometry_endz"],
-            "endxy":                    self.app.defaults["geometry_endxy"],
+            self.ui.combine_passes_cb.set_value(self.old_combine_state)
+            self.ui.combine_passes_cb.setDisabled(False)
 
-            "spindlespeed":             self.app.defaults["geometry_spindlespeed"],
-            "toolchangexy":             self.app.defaults["geometry_toolchangexy"],
-            "startz":                   self.app.defaults["geometry_startz"],
+            self.ui.forced_rest_iso_cb.setDisabled(True)
 
-            "area_exclusion":           self.app.defaults["geometry_area_exclusion"],
-            "area_shape":               self.app.defaults["geometry_area_shape"],
-            "area_strategy":            self.app.defaults["geometry_area_strategy"],
-            "area_overz":               float(self.app.defaults["geometry_area_overz"]),
+    def on_tooltable_cellwidget_change(self):
+        cw = self.sender()
+        assert isinstance(cw, QtWidgets.QComboBox), \
+            "Expected a QtWidgets.QComboBox, got %s" % isinstance(cw, QtWidgets.QComboBox)
 
-            "tools_iso_passes":         self.app.defaults["tools_iso_passes"],
-            "tools_iso_overlap":        self.app.defaults["tools_iso_overlap"],
-            "tools_iso_milling_type":   self.app.defaults["tools_iso_milling_type"],
-            "tools_iso_follow":         self.app.defaults["tools_iso_follow"],
-            "tools_iso_isotype":        self.app.defaults["tools_iso_isotype"],
+        cw_index = self.ui.tools_table.indexAt(cw.pos())
+        cw_row = cw_index.row()
+        cw_col = cw_index.column()
 
-            "tools_iso_rest":           self.app.defaults["tools_iso_rest"],
-            "tools_iso_combine_passes": self.app.defaults["tools_iso_combine_passes"],
-            "tools_iso_isoexcept":      self.app.defaults["tools_iso_isoexcept"],
-            "tools_iso_selection":      self.app.defaults["tools_iso_selection"],
-            "tools_iso_poly_ints":      self.app.defaults["tools_iso_poly_ints"],
-            "tools_iso_force":          self.app.defaults["tools_iso_force"],
-            "tools_iso_area_shape":     self.app.defaults["tools_iso_area_shape"]
-        }
+        current_uid = int(self.ui.tools_table.item(cw_row, 3).text())
 
-        try:
-            dias = [float(self.app.defaults["tools_iso_tooldia"])]
-        except (ValueError, TypeError):
-            dias = [float(eval(dia)) for dia in self.app.defaults["tools_iso_tooldia"].split(",") if dia != '']
+        # if the sender is in the column with index 2 then we update the tool_type key
+        if cw_col == 2:
+            tt = cw.currentText()
+            typ = 'Iso' if tt == 'V' else "Rough"
 
-        if not dias:
-            log.error("At least one tool diameter needed. Verify in Edit -> Preferences -> TOOLS -> Isolation Tools.")
-            return
+            self.iso_tools[current_uid].update({
+                'type': typ,
+                'tool_type': tt,
+            })
 
-        self.tooluid = 0
+    def on_tool_type(self, val):
+        if val == 'V':
+            self.ui.addtool_entry_lbl.setDisabled(True)
+            self.ui.addtool_entry.setDisabled(True)
+            self.ui.tipdialabel.show()
+            self.ui.tipdia_entry.show()
+            self.ui.tipanglelabel.show()
+            self.ui.tipangle_entry.show()
 
-        self.iso_tools.clear()
-        for tool_dia in dias:
-            self.tooluid += 1
-            self.iso_tools.update({
-                int(self.tooluid): {
-                    'tooldia': float('%.*f' % (self.decimals, tool_dia)),
-                    'offset': 'Path',
-                    'offset_value': 0.0,
-                    'type': 'Iso',
-                    'tool_type': self.tool_type_radio.get_value(),
-                    'data': deepcopy(self.default_data),
-                    'solid_geometry': []
-                }
+            self.on_calculate_tooldia()
+        else:
+            self.ui.addtool_entry_lbl.setDisabled(False)
+            self.ui.addtool_entry.setDisabled(False)
+            self.ui.tipdialabel.hide()
+            self.ui.tipdia_entry.hide()
+            self.ui.tipanglelabel.hide()
+            self.ui.tipangle_entry.hide()
+
+            self.ui.addtool_entry.set_value(self.old_tool_dia)
+
+    def on_calculate_tooldia(self):
+        if self.ui.tool_type_radio.get_value() == 'V':
+            tip_dia = float(self.ui.tipdia_entry.get_value())
+            tip_angle = float(self.ui.tipangle_entry.get_value()) / 2.0
+            cut_z = float(self.ui.cutz_entry.get_value())
+            cut_z = -cut_z if cut_z < 0 else cut_z
+
+            # calculated tool diameter so the cut_z parameter is obeyed
+            tool_dia = tip_dia + (2 * cut_z * math.tan(math.radians(tip_angle)))
+
+            # update the default_data so it is used in the iso_tools dict
+            self.default_data.update({
+                "vtipdia":      tip_dia,
+                "vtipangle":    (tip_angle * 2),
             })
 
-        self.obj_name = ""
-        self.grb_obj = None
+            self.ui.addtool_entry.set_value(tool_dia)
 
-        self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
-        self.units = self.app.defaults['units'].upper()
+            return tool_dia
+        else:
+            return float(self.ui.addtool_entry.get_value())
 
-    def build_ui(self):
-        self.ui_disconnect()
+    def on_tool_add(self, dia=None, muted=None):
+        self.blockSignals(True)
 
-        # updated units
         self.units = self.app.defaults['units'].upper()
 
-        sorted_tools = []
-        for k, v in self.iso_tools.items():
-            sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
-
-        order = self.order_radio.get_value()
-        if order == 'fwd':
-            sorted_tools.sort(reverse=False)
-        elif order == 'rev':
-            sorted_tools.sort(reverse=True)
+        if dia:
+            tool_dia = dia
         else:
-            pass
+            tool_dia = self.on_calculate_tooldia()
+            if tool_dia is None:
+                self.build_ui()
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Please enter a tool diameter to add, in Float format."))
+                return
 
-        n = len(sorted_tools)
-        self.tools_table.setRowCount(n)
-        tool_id = 0
+        tool_dia = float('%.*f' % (self.decimals, tool_dia))
 
-        for tool_sorted in sorted_tools:
-            for tooluid_key, tooluid_value in self.iso_tools.items():
-                if float('%.*f' % (self.decimals, tooluid_value['tooldia'])) == tool_sorted:
-                    tool_id += 1
-                    id_ = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
-                    id_.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
-                    row_no = tool_id - 1
-                    self.tools_table.setItem(row_no, 0, id_)  # Tool name/id
+        if tool_dia == 0:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Please enter a tool diameter with non-zero value, "
+                                                          "in Float format."))
+            return
 
-                    # Make sure that the drill diameter when in MM is with no more than 2 decimals
-                    # There are no drill bits in MM with more than 2 decimals diameter
-                    # For INCH the decimals should be no more than 4. There are no drills under 10mils
-                    dia = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, tooluid_value['tooldia']))
+        # construct a list of all 'tooluid' in the self.tools
+        tool_uid_list = []
+        for tooluid_key in self.iso_tools:
+            tool_uid_item = int(tooluid_key)
+            tool_uid_list.append(tool_uid_item)
 
-                    dia.setFlags(QtCore.Qt.ItemIsEnabled)
+        # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
+        if not tool_uid_list:
+            max_uid = 0
+        else:
+            max_uid = max(tool_uid_list)
+        self.tooluid = int(max_uid + 1)
 
-                    tool_type_item = FCComboBox()
-                    tool_type_item.addItems(self.tool_type_item_options)
+        tool_dias = []
+        for k, v in self.iso_tools.items():
+            for tool_v in v.keys():
+                if tool_v == 'tooldia':
+                    tool_dias.append(float('%.*f' % (self.decimals, (v[tool_v]))))
 
-                    # tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
-                    idx = tool_type_item.findText(tooluid_value['tool_type'])
-                    tool_type_item.setCurrentIndex(idx)
+        if float('%.*f' % (self.decimals, tool_dia)) in tool_dias:
+            if muted is None:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. Tool already in Tool Table."))
+            # self.ui.tools_table.itemChanged.connect(self.on_tool_edit)
+            self.blockSignals(False)
 
-                    tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
+            return
+        else:
+            if muted is None:
+                self.app.inform.emit('[success] %s' % _("New tool added to Tool Table."))
+            self.iso_tools.update({
+                int(self.tooluid): {
+                    'tooldia':          float('%.*f' % (self.decimals, tool_dia)),
+                    'offset':           'Path',
+                    'offset_value':     0.0,
+                    'type': '           Iso',
+                    'tool_type':        self.ui.tool_type_radio.get_value(),
+                    'data':             deepcopy(self.default_data),
+                    'solid_geometry':   []
+                }
+            })
 
-                    self.tools_table.setItem(row_no, 1, dia)  # Diameter
-                    self.tools_table.setCellWidget(row_no, 2, tool_type_item)
+        self.blockSignals(False)
+        self.build_ui()
 
-                    # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
-                    self.tools_table.setItem(row_no, 3, tool_uid_item)  # Tool unique ID
+        # select the tool just added
+        for row in range(self.ui.tools_table.rowCount()):
+            if int(self.ui.tools_table.item(row, 3).text()) == self.tooluid:
+                self.ui.tools_table.selectRow(row)
+                break
 
-        # make the diameter column editable
-        for row in range(tool_id):
-            self.tools_table.item(row, 1).setFlags(
-                QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+    def on_tool_edit(self, item):
+        self.blockSignals(True)
 
-        # all the tools are selected by default
-        self.tools_table.selectColumn(0)
-        #
-        self.tools_table.resizeColumnsToContents()
-        self.tools_table.resizeRowsToContents()
+        edited_row = item.row()
+        editeduid = int(self.ui.tools_table.item(edited_row, 3).text())
+        tool_dias = []
 
-        vertical_header = self.tools_table.verticalHeader()
-        vertical_header.hide()
-        self.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+        try:
+            new_tool_dia = float(self.ui.tools_table.item(edited_row, 1).text())
+        except ValueError:
+            # try to convert comma to decimal point. if it's still not working error message and return
+            try:
+                new_tool_dia = float(self.ui.tools_table.item(edited_row, 1).text().replace(',', '.'))
+            except ValueError:
+                self.app.inform.emit('[ERROR_NOTCL]  %s' % _("Wrong value format entered, use a number."))
+                self.blockSignals(False)
+                return
 
-        horizontal_header = self.tools_table.horizontalHeader()
-        horizontal_header.setMinimumSectionSize(10)
-        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(0, 20)
-        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+        for v in self.iso_tools.values():
+            tool_dias = [float('%.*f' % (self.decimals, v[tool_v])) for tool_v in v.keys() if tool_v == 'tooldia']
 
-        # self.tools_table.setSortingEnabled(True)
-        # sort by tool diameter
-        # self.tools_table.sortItems(1)
+        # identify the tool that was edited and get it's tooluid
+        if new_tool_dia not in tool_dias:
+            self.iso_tools[editeduid]['tooldia'] = deepcopy(float('%.*f' % (self.decimals, new_tool_dia)))
+            self.app.inform.emit('[success] %s' % _("Tool from Tool Table was edited."))
+            self.blockSignals(False)
+            self.build_ui()
+            return
 
-        self.tools_table.setMinimumHeight(self.tools_table.getHeight())
-        self.tools_table.setMaximumHeight(self.tools_table.getHeight())
+        # identify the old tool_dia and restore the text in tool table
+        for k, v in self.iso_tools.items():
+            if k == editeduid:
+                old_tool_dia = v['tooldia']
+                restore_dia_item = self.ui.tools_table.item(edited_row, 1)
+                restore_dia_item.setText(str(old_tool_dia))
+                break
 
-        self.ui_connect()
+        self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. New diameter value is already in the Tool Table."))
+        self.blockSignals(False)
+        self.build_ui()
 
-        # set the text on tool_data_label after loading the object
-        sel_rows = []
-        sel_items = self.tools_table.selectedItems()
-        for it in sel_items:
-            sel_rows.append(it.row())
-        if len(sel_rows) > 1:
-            self.tool_data_label.setText(
-                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
-            )
+    def on_tool_delete(self, rows_to_delete=None, all_tools=None):
+        """
+        Will delete a tool in the tool table
 
-    def ui_connect(self):
-        self.tools_table.itemChanged.connect(self.on_tool_edit)
+        :param rows_to_delete:      which rows to delete; can be a list
+        :param all_tools:           delete all tools in the tool table
+        :return:
+        """
+        self.blockSignals(True)
 
-        # rows selected
-        self.tools_table.clicked.connect(self.on_row_selection_change)
-        self.tools_table.horizontalHeader().sectionClicked.connect(self.on_row_selection_change)
+        deleted_tools_list = []
+
+        if all_tools:
+            self.iso_tools.clear()
+            self.blockSignals(False)
+            self.build_ui()
+            return
 
-        for row in range(self.tools_table.rowCount()):
+        if rows_to_delete:
             try:
-                self.tools_table.cellWidget(row, 2).currentIndexChanged.connect(self.on_tooltable_cellwidget_change)
-            except AttributeError:
-                pass
+                for row in rows_to_delete:
+                    tooluid_del = int(self.ui.tools_table.item(row, 3).text())
+                    deleted_tools_list.append(tooluid_del)
+            except TypeError:
+                tooluid_del = int(self.ui.tools_table.item(rows_to_delete, 3).text())
+                deleted_tools_list.append(tooluid_del)
 
-        self.tool_type_radio.activated_custom.connect(self.on_tool_type)
+            for t in deleted_tools_list:
+                self.iso_tools.pop(t, None)
 
-        for opt in self.form_fields:
-            current_widget = self.form_fields[opt]
-            if isinstance(current_widget, FCCheckBox):
-                current_widget.stateChanged.connect(self.form_to_storage)
-            if isinstance(current_widget, RadioSet):
-                current_widget.activated_custom.connect(self.form_to_storage)
-            elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner):
-                current_widget.returnPressed.connect(self.form_to_storage)
-            elif isinstance(current_widget, FCComboBox):
-                current_widget.currentIndexChanged.connect(self.form_to_storage)
+            self.blockSignals(False)
+            self.build_ui()
+            return
 
-        self.rest_cb.stateChanged.connect(self.on_rest_machining_check)
-        self.order_radio.activated_custom[str].connect(self.on_order_changed)
+        try:
+            if self.ui.tools_table.selectedItems():
+                for row_sel in self.ui.tools_table.selectedItems():
+                    row = row_sel.row()
+                    if row < 0:
+                        continue
+                    tooluid_del = int(self.ui.tools_table.item(row, 3).text())
+                    deleted_tools_list.append(tooluid_del)
 
-    def ui_disconnect(self):
+                for t in deleted_tools_list:
+                    self.iso_tools.pop(t, None)
 
-        try:
-            # if connected, disconnect the signal from the slot on item_changed as it creates issues
-            self.tools_table.itemChanged.disconnect()
-        except (TypeError, AttributeError):
-            pass
+        except AttributeError:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Delete failed. Select a tool to delete."))
+            self.blockSignals(False)
+            return
+        except Exception as e:
+            log.debug(str(e))
 
-        try:
-            # if connected, disconnect the signal from the slot on item_changed as it creates issues
-            self.tool_type_radio.activated_custom.disconnect()
-        except (TypeError, AttributeError):
-            pass
+        self.app.inform.emit('[success] %s' % _("Tool(s) deleted from Tool Table."))
+        self.blockSignals(False)
+        self.build_ui()
 
-        for row in range(self.tools_table.rowCount()):
+    def on_generate_buffer(self):
+        self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Buffering solid geometry"))
 
-            try:
-                self.tools_table.cellWidget(row, 2).currentIndexChanged.disconnect()
-            except (TypeError, AttributeError):
-                pass
+        self.obj_name = self.ui.object_combo.currentText()
 
-        for opt in self.form_fields:
-            current_widget = self.form_fields[opt]
-            if isinstance(current_widget, FCCheckBox):
-                try:
-                    current_widget.stateChanged.disconnect(self.form_to_storage)
-                except (TypeError, ValueError):
-                    pass
-            if isinstance(current_widget, RadioSet):
-                try:
-                    current_widget.activated_custom.disconnect(self.form_to_storage)
-                except (TypeError, ValueError):
-                    pass
-            elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner):
-                try:
-                    current_widget.returnPressed.disconnect(self.form_to_storage)
-                except (TypeError, ValueError):
-                    pass
-            elif isinstance(current_widget, FCComboBox):
-                try:
-                    current_widget.currentIndexChanged.disconnect(self.form_to_storage)
-                except (TypeError, ValueError):
-                    pass
-
-        try:
-            self.rest_cb.stateChanged.disconnect()
-        except (TypeError, ValueError):
-            pass
-        try:
-            self.order_radio.activated_custom[str].disconnect()
-        except (TypeError, ValueError):
-            pass
-
-        # rows selected
-        try:
-            self.tools_table.clicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
+        # Get source object.
         try:
-            self.tools_table.horizontalHeader().sectionClicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
+            self.grb_obj = self.app.collection.get_by_name(self.obj_name)
+        except Exception as e:
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(self.obj_name)))
+            return "Could not retrieve object: %s with error: %s" % (self.obj_name, str(e))
 
-    def on_row_selection_change(self):
-        self.blockSignals(True)
+        if self.grb_obj is None:
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name)))
+            return
 
-        sel_rows = [it.row() for it in self.tools_table.selectedItems()]
-        # sel_rows = sorted(set(index.row() for index in self.tools_table.selectedIndexes()))
+        def buffer_task(app_obj):
+            with app_obj.proc_container.new('%s...' % _("Buffering")):
+                if isinstance(self.grb_obj.solid_geometry, list):
+                    self.grb_obj.solid_geometry = MultiPolygon(self.grb_obj.solid_geometry)
 
-        if not sel_rows:
-            sel_rows = [0]
+                self.grb_obj.solid_geometry = self.grb_obj.solid_geometry.buffer(0.0000001)
+                self.grb_obj.solid_geometry = self.grb_obj.solid_geometry.buffer(-0.0000001)
+                app_obj.inform.emit('[success] %s.' % _("Done"))
+                self.grb_obj.plot_single_object.emit()
 
-        for current_row in sel_rows:
-            # populate the form with the data from the tool associated with the row parameter
-            try:
-                item = self.tools_table.item(current_row, 3)
-                if item is not None:
-                    tooluid = int(item.text())
-                else:
-                    return
-            except Exception as e:
-                log.debug("Tool missing. Add a tool in the Tool Table. %s" % str(e))
-                return
+        self.app.worker_task.emit({'fcn': buffer_task, 'params': [self.app]})
 
-            # update the QLabel that shows for which Tool we have the parameters in the UI form
-            if len(sel_rows) == 1:
-                cr = current_row + 1
-                self.tool_data_label.setText(
-                    "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), cr)
-                )
-                try:
-                    # set the form with data from the newly selected tool
-                    for tooluid_key, tooluid_value in list(self.iso_tools.items()):
-                        if int(tooluid_key) == tooluid:
-                            for key, value in tooluid_value.items():
-                                if key == 'data':
-                                    form_value_storage = tooluid_value[key]
-                                    self.storage_to_form(form_value_storage)
-                except Exception as e:
-                    log.debug("ToolIsolation ---> update_ui() " + str(e))
-            else:
-                self.tool_data_label.setText(
-                    "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
-                )
+    def on_iso_button_click(self):
 
-        self.blockSignals(False)
+        self.obj_name = self.ui.object_combo.currentText()
 
-    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:
-                        log.debug("ToolIsolation.storage_to_form() --> %s" % str(e))
-                        pass
+        # Get source object.
+        try:
+            self.grb_obj = self.app.collection.get_by_name(self.obj_name)
+        except Exception as e:
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(self.obj_name)))
+            return "Could not retrieve object: %s with error: %s" % (self.obj_name, str(e))
 
-    def form_to_storage(self):
-        if self.tools_table.rowCount() == 0:
-            # there is no tool in tool table so we can't save the GUI elements values to storage
+        if self.grb_obj is None:
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name)))
             return
 
-        self.blockSignals(True)
+        def worker_task(iso_obj):
+            with self.app.proc_container.new(_("Isolating...")):
+                self.isolate_handler(iso_obj)
 
-        widget_changed = self.sender()
-        wdg_objname = widget_changed.objectName()
-        option_changed = self.name2option[wdg_objname]
+        self.app.worker_task.emit({'fcn': worker_task, 'params': [self.grb_obj]})
 
-        # row = self.tools_table.currentRow()
-        rows = sorted(set(index.row() for index in self.tools_table.selectedIndexes()))
-        for row in rows:
-            if row < 0:
-                row = 0
-            tooluid_item = int(self.tools_table.item(row, 3).text())
+    def follow_geo(self, followed_obj, outname):
+        """
+        Creates a geometry object "following" the gerber paths.
 
-            for tooluid_key, tooluid_val in self.iso_tools.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
+        :param followed_obj:    Gerber object for which to generate the follow geometry
+        :type followed_obj:     AppObjects.FlatCAMGerber.GerberObject
+        :param outname:         Nme of the resulting Geometry object
+        :type outname:          str
+        :return: None
+        """
 
-        self.blockSignals(False)
+        def follow_init(follow_obj, app_obj):
+            # Propagate options
+            follow_obj.options["cnctooldia"] = str(tooldia)
+            follow_obj.solid_geometry = self.grb_obj.follow_geometry
+            app_obj.inform.emit('[success] %s.' % _("Following geometry was generated"))
 
-    def on_apply_param_to_all_clicked(self):
-        if self.tools_table.rowCount() == 0:
-            # there is no tool in tool table so we can't save the GUI elements values to storage
-            log.debug("ToolIsolation.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.")
-            return
+        # in the end toggle the visibility of the origin object so we can see the generated Geometry
+        followed_obj.ui.plot_cb.set_value(False)
+        follow_name = outname
 
-        self.blockSignals(True)
+        for tool in self.iso_tools:
+            tooldia = self.iso_tools[tool]['tooldia']
+            new_name = "%s_%.*f" % (follow_name, self.decimals, tooldia)
 
-        row = self.tools_table.currentRow()
-        if row < 0:
-            row = 0
+            follow_state = self.iso_tools[tool]['data']['tools_iso_follow']
+            if follow_state:
+                ret = self.app.app_obj.new_object("geometry", new_name, follow_init)
+                if ret == 'fail':
+                    self.app.inform.emit("[ERROR_NOTCL] %s: %.*f" % (
+                        _("Failed to create Follow Geometry with tool diameter"), self.decimals, tooldia))
+                else:
+                    self.app.inform.emit("[success] %s: %.*f" % (
+                        _("Follow Geometry was created with tool diameter"), self.decimals, tooldia))
 
-        tooluid_item = int(self.tools_table.item(row, 3).text())
-        temp_tool_data = {}
+    def isolate_handler(self, isolated_obj):
+        """
+        Creates a geometry object with paths around the gerber features.
 
-        for tooluid_key, tooluid_val in self.iso_tools.items():
-            if int(tooluid_key) == tooluid_item:
-                # this will hold the 'data' key of the self.tools[tool] dictionary that corresponds to
-                # the current row in the tool table
-                temp_tool_data = tooluid_val['data']
-                break
+        :param isolated_obj:    Gerber object for which to generate the isolating routing geometry
+        :type isolated_obj:     AppObjects.FlatCAMGerber.GerberObject
+        :return: None
+        """
+        selection = self.ui.select_combo.get_value()
 
-        for tooluid_key, tooluid_val in self.iso_tools.items():
-            tooluid_val['data'] = deepcopy(temp_tool_data)
+        if selection == _("All"):
+            self.isolate(isolated_obj=isolated_obj)
+        elif selection == _("Area Selection"):
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
 
-        self.app.inform.emit('[success] %s' % _("Current Tool parameters were applied to all tools."))
-        self.blockSignals(False)
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
 
-    def on_add_tool_by_key(self):
-        tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
-                                       text='%s:' % _('Enter a Tool Diameter'),
-                                       min=0.0001, max=9999.9999, decimals=self.decimals)
-        tool_add_popup.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/letter_t_32.png'))
+            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
+            self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
+            self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
 
-        val, ok = tool_add_popup.get_value()
-        if ok:
-            if float(val) == 0:
-                self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                     _("Please enter a tool diameter with non-zero value, in Float format."))
-                return
-            self.on_tool_add(dia=float(val))
-        else:
-            self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Adding Tool cancelled"))
+            # disconnect flags
+            self.area_sel_disconnect_flag = True
 
-    def on_tooldia_updated(self):
-        if self.tool_type_radio.get_value() == 'C1':
-            self.old_tool_dia = self.addtool_entry.get_value()
+        elif selection == _("Polygon Selection"):
+            # disengage the grid snapping since it may be hard to click on polygons with grid snapping on
+            if self.app.ui.grid_snap_btn.isChecked():
+                self.grid_status_memory = True
+                self.app.ui.grid_snap_btn.trigger()
+            else:
+                self.grid_status_memory = False
 
-    def on_reference_combo_changed(self):
-        obj_type = self.reference_combo_type.currentIndex()
-        self.reference_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.reference_combo.setCurrentIndex(0)
-        self.reference_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
-        }[self.reference_combo_type.get_value()]
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
+            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_poly_mouse_click_release)
+            self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
 
-    def on_toggle_reference(self):
-        val = self.select_combo.get_value()
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release',
+                                                           self.app.on_mouse_click_release_over_plot)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
 
-        if val == _("All"):
-            self.reference_combo.hide()
-            self.reference_combo_label.hide()
-            self.reference_combo_type.hide()
-            self.reference_combo_type_label.hide()
-            self.area_shape_label.hide()
-            self.area_shape_radio.hide()
-            self.poly_int_cb.hide()
+            # disconnect flags
+            self.poly_sel_disconnect_flag = True
 
-            # disable rest-machining for area painting
-            self.rest_cb.setDisabled(False)
-        elif val == _("Area Selection"):
-            self.reference_combo.hide()
-            self.reference_combo_label.hide()
-            self.reference_combo_type.hide()
-            self.reference_combo_type_label.hide()
-            self.area_shape_label.show()
-            self.area_shape_radio.show()
-            self.poly_int_cb.hide()
+        elif selection == _("Reference Object"):
+            ref_obj = self.app.collection.get_by_name(self.ui.reference_combo.get_value())
+            ref_geo = cascaded_union(ref_obj.solid_geometry)
+            use_geo = cascaded_union(isolated_obj.solid_geometry).difference(ref_geo)
+            self.isolate(isolated_obj=isolated_obj, geometry=use_geo)
 
-            # disable rest-machining for area isolation
-            self.rest_cb.set_value(False)
-            self.rest_cb.setDisabled(True)
-        elif val == _("Polygon Selection"):
-            self.reference_combo.hide()
-            self.reference_combo_label.hide()
-            self.reference_combo_type.hide()
-            self.reference_combo_type_label.hide()
-            self.area_shape_label.hide()
-            self.area_shape_radio.hide()
-            self.poly_int_cb.show()
-        else:
-            self.reference_combo.show()
-            self.reference_combo_label.show()
-            self.reference_combo_type.show()
-            self.reference_combo_type_label.show()
-            self.area_shape_label.hide()
-            self.area_shape_radio.hide()
-            self.poly_int_cb.hide()
+    def isolate(self, isolated_obj, geometry=None, limited_area=None, negative_dia=None, plot=True):
+        """
+        Creates an isolation routing geometry object in the project.
 
-            # disable rest-machining for area painting
-            self.rest_cb.setDisabled(False)
+        :param isolated_obj:    Gerber object for which to generate the isolating routing geometry
+        :type isolated_obj:     AppObjects.FlatCAMGerber.GerberObject
+        :param geometry:        specific geometry to isolate
+        :type geometry:         List of Shapely polygon
+        :param limited_area:    if not None isolate only this area
+        :type limited_area:     Shapely Polygon or a list of them
+        :param negative_dia:    isolate the geometry with a negative value for the tool diameter
+        :type negative_dia:     bool
+        :param plot:            if to plot the resulting geometry object
+        :type plot:             bool
+        :return: None
+        """
 
-    def on_order_changed(self, order):
-        if order != 'no':
-            self.build_ui()
+        combine = self.ui.combine_passes_cb.get_value()
+        tools_storage = self.iso_tools
 
-    def on_rest_machining_check(self, state):
-        if state:
-            self.order_radio.set_value('rev')
-            self.order_label.setDisabled(True)
-            self.order_radio.setDisabled(True)
+        # update the Common Parameters valuse in the self.iso_tools
+        for tool_iso in self.iso_tools:
+            for key in self.iso_tools[tool_iso]:
+                if key == 'data':
+                    self.iso_tools[tool_iso][key]["tools_iso_rest"] = self.ui.rest_cb.get_value()
+                    self.iso_tools[tool_iso][key]["tools_iso_combine_passes"] = combine
+                    self.iso_tools[tool_iso][key]["tools_iso_isoexcept"] = self.ui.except_cb.get_value()
+                    self.iso_tools[tool_iso][key]["tools_iso_selection"] = self.ui.select_combo.get_value()
+                    self.iso_tools[tool_iso][key]["tools_iso_area_shape"] = self.ui.area_shape_radio.get_value()
 
-            self.old_combine_state = self.combine_passes_cb.get_value()
-            self.combine_passes_cb.set_value(True)
-            self.combine_passes_cb.setDisabled(True)
+        if combine:
+            if self.ui.rest_cb.get_value():
+                self.combined_rest(iso_obj=isolated_obj, iso2geo=geometry, tools_storage=tools_storage,
+                                   lim_area=limited_area, negative_dia=negative_dia, plot=plot)
+            else:
+                self.combined_normal(iso_obj=isolated_obj, iso2geo=geometry, tools_storage=tools_storage,
+                                     lim_area=limited_area, negative_dia=negative_dia, plot=plot)
 
-            self.forced_rest_iso_cb.setDisabled(False)
         else:
-            self.order_label.setDisabled(False)
-            self.order_radio.setDisabled(False)
-
-            self.combine_passes_cb.set_value(self.old_combine_state)
-            self.combine_passes_cb.setDisabled(False)
+            prog_plot = self.app.defaults["tools_iso_plotting"]
 
-            self.forced_rest_iso_cb.setDisabled(True)
+            for tool in tools_storage:
+                tool_data = tools_storage[tool]['data']
+                to_follow = tool_data['tools_iso_follow']
 
-    def on_tooltable_cellwidget_change(self):
-        cw = self.sender()
-        assert isinstance(cw, QtWidgets.QComboBox), \
-            "Expected a QtWidgets.QComboBox, got %s" % isinstance(cw, QtWidgets.QComboBox)
+                work_geo = geometry
+                if work_geo is None:
+                    work_geo = isolated_obj.follow_geometry if to_follow else isolated_obj.solid_geometry
 
-        cw_index = self.tools_table.indexAt(cw.pos())
-        cw_row = cw_index.row()
-        cw_col = cw_index.column()
+                iso_t = {
+                    'ext': 0,
+                    'int': 1,
+                    'full': 2
+                }[tool_data['tools_iso_isotype']]
 
-        current_uid = int(self.tools_table.item(cw_row, 3).text())
+                passes = tool_data['tools_iso_passes']
+                overlap = tool_data['tools_iso_overlap']
+                overlap /= 100.0
 
-        # if the sender is in the column with index 2 then we update the tool_type key
-        if cw_col == 2:
-            tt = cw.currentText()
-            typ = 'Iso' if tt == 'V' else "Rough"
+                milling_type = tool_data['tools_iso_milling_type']
 
-            self.iso_tools[current_uid].update({
-                'type': typ,
-                'tool_type': tt,
-            })
+                iso_except = self.ui.except_cb.get_value()
 
-    def on_tool_type(self, val):
-        if val == 'V':
-            self.addtool_entry_lbl.setDisabled(True)
-            self.addtool_entry.setDisabled(True)
-            self.tipdialabel.show()
-            self.tipdia_entry.show()
-            self.tipanglelabel.show()
-            self.tipangle_entry.show()
+                for i in range(passes):
+                    tool_dia = tools_storage[tool]['tooldia']
+                    tool_type = tools_storage[tool]['tool_type']
 
-            self.on_calculate_tooldia()
-        else:
-            self.addtool_entry_lbl.setDisabled(False)
-            self.addtool_entry.setDisabled(False)
-            self.tipdialabel.hide()
-            self.tipdia_entry.hide()
-            self.tipanglelabel.hide()
-            self.tipangle_entry.hide()
+                    iso_offset = tool_dia * ((2 * i + 1) / 2.0000001) - (i * overlap * tool_dia)
+                    if negative_dia:
+                        iso_offset = -iso_offset
 
-            self.addtool_entry.set_value(self.old_tool_dia)
+                    outname = "%s_%.*f" % (isolated_obj.options["name"], self.decimals, float(tool_dia))
 
-    def on_calculate_tooldia(self):
-        if self.tool_type_radio.get_value() == 'V':
-            tip_dia = float(self.tipdia_entry.get_value())
-            tip_angle = float(self.tipangle_entry.get_value()) / 2.0
-            cut_z = float(self.cutz_entry.get_value())
-            cut_z = -cut_z if cut_z < 0 else cut_z
+                    if passes > 1:
+                        iso_name = outname + "_iso" + str(i + 1)
+                        if iso_t == 0:
+                            iso_name = outname + "_ext_iso" + str(i + 1)
+                        elif iso_t == 1:
+                            iso_name = outname + "_int_iso" + str(i + 1)
+                    else:
+                        iso_name = outname + "_iso"
+                        if iso_t == 0:
+                            iso_name = outname + "_ext_iso"
+                        elif iso_t == 1:
+                            iso_name = outname + "_int_iso"
 
-            # calculated tool diameter so the cut_z parameter is obeyed
-            tool_dia = tip_dia + (2 * cut_z * math.tan(math.radians(tip_angle)))
+                    # if milling type is climb then the move is counter-clockwise around features
+                    mill_dir = 1 if milling_type == 'cl' else 0
 
-            # update the default_data so it is used in the iso_tools dict
-            self.default_data.update({
-                "vtipdia": tip_dia,
-                "vtipangle": (tip_angle * 2),
-            })
+                    iso_geo = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
+                                                     follow=to_follow, nr_passes=i, prog_plot=prog_plot)
+                    if iso_geo == 'fail':
+                        self.app.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
+                        continue
 
-            self.addtool_entry.set_value(tool_dia)
+                    # ############################################################
+                    # ########## AREA SUBTRACTION ################################
+                    # ############################################################
+                    if iso_except:
+                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                        iso_geo = self.area_subtraction(iso_geo)
 
-            return tool_dia
-        else:
-            return float(self.addtool_entry.get_value())
+                    if limited_area:
+                        self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
+                        iso_geo = self.area_intersection(iso_geo, intersection_geo=limited_area)
 
-    def on_tool_add(self, dia=None, muted=None):
-        self.blockSignals(True)
+                    # make sure that no empty geometry element is in the solid_geometry
+                    new_solid_geo = [geo for geo in iso_geo if not geo.is_empty]
 
-        self.units = self.app.defaults['units'].upper()
+                    tool_data.update({
+                        "name": iso_name,
+                    })
 
-        if dia:
-            tool_dia = dia
-        else:
-            tool_dia = self.on_calculate_tooldia()
-            if tool_dia is None:
-                self.build_ui()
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Please enter a tool diameter to add, in Float format."))
-                return
+                    def iso_init(geo_obj, fc_obj):
+                        # Propagate options
+                        geo_obj.options["cnctooldia"] = str(tool_dia)
+                        geo_obj.solid_geometry = deepcopy(new_solid_geo)
 
-        tool_dia = float('%.*f' % (self.decimals, tool_dia))
+                        # ############################################################
+                        # ########## AREA SUBTRACTION ################################
+                        # ############################################################
+                        if self.ui.except_cb.get_value():
+                            self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                            geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
 
-        if tool_dia == 0:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Please enter a tool diameter with non-zero value, "
-                                                          "in Float format."))
-            return
+                        geo_obj.tools = {}
+                        geo_obj.tools['1'] = {}
+                        geo_obj.tools.update({
+                            '1': {
+                                'tooldia':          float(tool_dia),
+                                'offset':           'Path',
+                                'offset_value':     0.0,
+                                'type':             _('Rough'),
+                                'tool_type':        tool_type,
+                                'data':             tool_data,
+                                'solid_geometry':   geo_obj.solid_geometry
+                            }
+                        })
 
-        # construct a list of all 'tooluid' in the self.tools
-        tool_uid_list = []
-        for tooluid_key in self.iso_tools:
-            tool_uid_item = int(tooluid_key)
-            tool_uid_list.append(tool_uid_item)
+                        # detect if solid_geometry is empty and this require list flattening which is "heavy"
+                        # or just looking in the lists (they are one level depth) and if any is not empty
+                        # proceed with object creation, if there are empty and the number of them is the length
+                        # of the list then we have an empty solid_geometry which should raise a Custom Exception
+                        empty_cnt = 0
+                        if not isinstance(geo_obj.solid_geometry, list):
+                            geo_obj.solid_geometry = [geo_obj.solid_geometry]
 
-        # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
-        if not tool_uid_list:
-            max_uid = 0
-        else:
-            max_uid = max(tool_uid_list)
-        self.tooluid = int(max_uid + 1)
+                        for g in geo_obj.solid_geometry:
+                            if g:
+                                break
+                            else:
+                                empty_cnt += 1
 
-        tool_dias = []
-        for k, v in self.iso_tools.items():
-            for tool_v in v.keys():
-                if tool_v == 'tooldia':
-                    tool_dias.append(float('%.*f' % (self.decimals, (v[tool_v]))))
+                        if empty_cnt == len(geo_obj.solid_geometry):
+                            fc_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (
+                                _("Empty Geometry in"), geo_obj.options["name"]))
+                            return 'fail'
+                        else:
+                            fc_obj.inform.emit('[success] %s: %s' %
+                                               (_("Isolation geometry created"), geo_obj.options["name"]))
+                        geo_obj.multigeo = False
 
-        if float('%.*f' % (self.decimals, tool_dia)) in tool_dias:
-            if muted is None:
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. Tool already in Tool Table."))
-            # self.tools_table.itemChanged.connect(self.on_tool_edit)
-            self.blockSignals(False)
+                    self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
 
-            return
-        else:
-            if muted is None:
-                self.app.inform.emit('[success] %s' % _("New tool added to Tool Table."))
-            self.iso_tools.update({
-                int(self.tooluid): {
-                    'tooldia': float('%.*f' % (self.decimals, tool_dia)),
-                    'offset': 'Path',
-                    'offset_value': 0.0,
-                    'type': 'Iso',
-                    'tool_type': self.tool_type_radio.get_value(),
-                    'data': deepcopy(self.default_data),
-                    'solid_geometry': []
-                }
-            })
+            # clean the progressive plotted shapes if it was used
 
-        self.blockSignals(False)
-        self.build_ui()
+            if prog_plot == 'progressive':
+                self.temp_shapes.clear(update=True)
 
-    def on_tool_edit(self):
-        self.blockSignals(True)
+        # Switch notebook to Selected page
+        self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
 
-        old_tool_dia = ''
-        tool_dias = []
-        for k, v in self.iso_tools.items():
-            for tool_v in v.keys():
-                if tool_v == 'tooldia':
-                    tool_dias.append(float('%.*f' % (self.decimals, v[tool_v])))
+    def combined_rest(self, iso_obj, iso2geo, tools_storage, lim_area, negative_dia=None, plot=True):
+        """
+        Isolate the provided Gerber object using "rest machining" strategy
 
-        for row in range(self.tools_table.rowCount()):
+        :param iso_obj:         the isolated Gerber object
+        :type iso_obj:          AppObjects.FlatCAMGerber.GerberObject
+        :param iso2geo:         specific geometry to isolate
+        :type iso2geo:          list of Shapely Polygon
+        :param tools_storage:   a dictionary that holds the tools and geometry
+        :type tools_storage:    dict
+        :param lim_area:        if not None restrict isolation to this area
+        :type lim_area:         Shapely Polygon or a list of them
+        :param negative_dia:    isolate the geometry with a negative value for the tool diameter
+        :type negative_dia:     bool
+        :param plot:            if to plot the resulting geometry object
+        :type plot:             bool
+        :return:                Isolated solid geometry
+        :rtype:
+        """
 
-            try:
-                new_tool_dia = float(self.tools_table.item(row, 1).text())
-            except ValueError:
-                # try to convert comma to decimal point. if it's still not working error message and return
-                try:
-                    new_tool_dia = float(self.tools_table.item(row, 1).text().replace(',', '.'))
-                except ValueError:
-                    self.app.inform.emit('[ERROR_NOTCL]  %s' % _("Wrong value format entered, use a number."))
-                    self.blockSignals(False)
-                    return
+        log.debug("ToolIsolation.combine_rest()")
 
-            tooluid = int(self.tools_table.item(row, 3).text())
+        total_solid_geometry = []
 
-            # identify the tool that was edited and get it's tooluid
-            if new_tool_dia not in tool_dias:
-                self.iso_tools[tooluid]['tooldia'] = new_tool_dia
-                self.app.inform.emit('[success] %s' % _("Tool from Tool Table was edited."))
-                self.blockSignals(False)
-                self.build_ui()
-                return
-            else:
-                # identify the old tool_dia and restore the text in tool table
-                for k, v in self.iso_tools.items():
-                    if k == tooluid:
-                        old_tool_dia = v['tooldia']
-                        break
-                restore_dia_item = self.tools_table.item(row, 1)
-                restore_dia_item.setText(str(old_tool_dia))
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. "
-                                                              "New diameter value is already in the Tool Table."))
-        self.blockSignals(False)
-        self.build_ui()
+        iso_name = iso_obj.options["name"] + '_iso_rest'
+        work_geo = iso_obj.solid_geometry if iso2geo is None else iso2geo
 
-    def on_tool_delete(self, rows_to_delete=None, all_tools=None):
-        """
-        Will delete a tool in the tool table
+        sorted_tools = []
+        for k, v in self.iso_tools.items():
+            sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
 
-        :param rows_to_delete: which rows to delete; can be a list
-        :param all_tools: delete all tools in the tool table
-        :return:
-        """
-        self.blockSignals(True)
+        order = self.ui.order_radio.get_value()
+        if order == 'fwd':
+            sorted_tools.sort(reverse=False)
+        elif order == 'rev':
+            sorted_tools.sort(reverse=True)
+        else:
+            pass
 
-        deleted_tools_list = []
+        # decide to use "progressive" or "normal" plotting
+        prog_plot = self.app.defaults["tools_iso_plotting"]
 
-        if all_tools:
-            self.iso_tools.clear()
-            self.blockSignals(False)
-            self.build_ui()
-            return
+        for sorted_tool in sorted_tools:
+            for tool in tools_storage:
+                if float('%.*f' % (self.decimals, tools_storage[tool]['tooldia'])) == sorted_tool:
 
-        if rows_to_delete:
-            try:
-                for row in rows_to_delete:
-                    tooluid_del = int(self.tools_table.item(row, 3).text())
-                    deleted_tools_list.append(tooluid_del)
-            except TypeError:
-                tooluid_del = int(self.tools_table.item(rows_to_delete, 3).text())
-                deleted_tools_list.append(tooluid_del)
+                    tool_dia = tools_storage[tool]['tooldia']
+                    tool_type = tools_storage[tool]['tool_type']
+                    tool_data = tools_storage[tool]['data']
 
-            for t in deleted_tools_list:
-                self.iso_tools.pop(t, None)
-
-            self.blockSignals(False)
-            self.build_ui()
-            return
-
-        try:
-            if self.tools_table.selectedItems():
-                for row_sel in self.tools_table.selectedItems():
-                    row = row_sel.row()
-                    if row < 0:
-                        continue
-                    tooluid_del = int(self.tools_table.item(row, 3).text())
-                    deleted_tools_list.append(tooluid_del)
-
-                for t in deleted_tools_list:
-                    self.iso_tools.pop(t, None)
+                    passes = tool_data['tools_iso_passes']
+                    overlap = tool_data['tools_iso_overlap']
+                    overlap /= 100.0
 
-        except AttributeError:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Delete failed. Select a tool to delete."))
-            self.blockSignals(False)
-            return
-        except Exception as e:
-            log.debug(str(e))
+                    milling_type = tool_data['tools_iso_milling_type']
+                    # if milling type is climb then the move is counter-clockwise around features
+                    mill_dir = True if milling_type == 'cl' else False
+                    iso_t = {
+                        'ext': 0,
+                        'int': 1,
+                        'full': 2
+                    }[tool_data['tools_iso_isotype']]
 
-        self.app.inform.emit('[success] %s' % _("Tool(s) deleted from Tool Table."))
-        self.blockSignals(False)
-        self.build_ui()
+                    forced_rest = self.ui.forced_rest_iso_cb.get_value()
+                    iso_except = self.ui.except_cb.get_value()
 
-    def on_generate_buffer(self):
-        self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Buffering solid geometry"))
+                    outname = "%s_%.*f" % (iso_obj.options["name"], self.decimals, float(tool_dia))
+                    internal_name = outname + "_iso"
+                    if iso_t == 0:
+                        internal_name = outname + "_ext_iso"
+                    elif iso_t == 1:
+                        internal_name = outname + "_int_iso"
 
-        self.obj_name = self.object_combo.currentText()
+                    tool_data.update({
+                        "name": internal_name,
+                    })
 
-        # Get source object.
-        try:
-            self.grb_obj = self.app.collection.get_by_name(self.obj_name)
-        except Exception as e:
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(self.obj_name)))
-            return "Could not retrieve object: %s with error: %s" % (self.obj_name, str(e))
+                    solid_geo, work_geo = self.generate_rest_geometry(geometry=work_geo, tooldia=tool_dia,
+                                                                      passes=passes, overlap=overlap, invert=mill_dir,
+                                                                      env_iso_type=iso_t, negative_dia=negative_dia,
+                                                                      forced_rest=forced_rest,
+                                                                      prog_plot=prog_plot,
+                                                                      prog_plot_handler=self.plot_temp_shapes)
 
-        if self.grb_obj is None:
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name)))
-            return
+                    # ############################################################
+                    # ########## AREA SUBTRACTION ################################
+                    # ############################################################
+                    if iso_except:
+                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                        solid_geo = self.area_subtraction(solid_geo)
 
-        def buffer_task():
-            with self.app.proc_container.new('%s...' % _("Buffering")):
-                if isinstance(self.grb_obj.solid_geometry, list):
-                    self.grb_obj.solid_geometry = MultiPolygon(self.grb_obj.solid_geometry)
+                    if lim_area:
+                        self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
+                        solid_geo = self.area_intersection(solid_geo, intersection_geo=lim_area)
 
-                self.grb_obj.solid_geometry = self.grb_obj.solid_geometry.buffer(0.0000001)
-                self.grb_obj.solid_geometry = self.grb_obj.solid_geometry.buffer(-0.0000001)
-                self.app.inform.emit('[success] %s.' % _("Done"))
-                self.grb_obj.plot_single_object.emit()
+                    # make sure that no empty geometry element is in the solid_geometry
+                    new_solid_geo = [geo for geo in solid_geo if not geo.is_empty]
 
-        self.app.worker_task.emit({'fcn': buffer_task, 'params': []})
+                    tools_storage.update({
+                        tool: {
+                            'tooldia':          float(tool_dia),
+                            'offset':           'Path',
+                            'offset_value':     0.0,
+                            'type':             _('Rough'),
+                            'tool_type':        tool_type,
+                            'data':             tool_data,
+                            'solid_geometry':   deepcopy(new_solid_geo)
+                        }
+                    })
 
-    def on_iso_button_click(self):
+                    total_solid_geometry += new_solid_geo
 
-        self.obj_name = self.object_combo.currentText()
+                    # if the geometry is all isolated
+                    if not work_geo:
+                        break
 
-        # Get source object.
-        try:
-            self.grb_obj = self.app.collection.get_by_name(self.obj_name)
-        except Exception as e:
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(self.obj_name)))
-            return "Could not retrieve object: %s with error: %s" % (self.obj_name, str(e))
+        # clean the progressive plotted shapes if it was used
+        if self.app.defaults["tools_iso_plotting"] == 'progressive':
+            self.temp_shapes.clear(update=True)
 
-        if self.grb_obj is None:
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name)))
-            return
+        def iso_init(geo_obj, app_obj):
+            geo_obj.options["cnctooldia"] = str(tool_dia)
 
-        def worker_task(iso_obj):
-            with self.app.proc_container.new(_("Isolating...")):
-                self.isolate_handler(iso_obj)
+            geo_obj.tools = dict(tools_storage)
+            geo_obj.solid_geometry = total_solid_geometry
+            # even if combine is checked, one pass is still single-geo
 
-        self.app.worker_task.emit({'fcn': worker_task, 'params': [self.grb_obj]})
+            # remove the tools that have no geometry
+            for geo_tool in list(geo_obj.tools.keys()):
+                if not geo_obj.tools[geo_tool]['solid_geometry']:
+                    geo_obj.tools.pop(geo_tool, None)
 
-    def follow_geo(self, followed_obj, outname):
-        """
-        Creates a geometry object "following" the gerber paths.
+            if len(tools_storage) > 1:
+                geo_obj.multigeo = True
+            else:
+                for ky in tools_storage.keys():
+                    passes_no = float(tools_storage[ky]['data']['tools_iso_passes'])
+                    geo_obj.multigeo = True if passes_no > 1 else False
+                    break
 
-        :param followed_obj:    Gerber object for which to generate the follow geometry
-        :type followed_obj:     AppObjects.FlatCAMGerber.GerberObject
-        :param outname:         Nme of the resulting Geometry object
-        :type outname:          str
-        :return: None
-        """
+            # detect if solid_geometry is empty and this require list flattening which is "heavy"
+            # or just looking in the lists (they are one level depth) and if any is not empty
+            # proceed with object creation, if there are empty and the number of them is the length
+            # of the list then we have an empty solid_geometry which should raise a Custom Exception
+            empty_cnt = 0
+            if not isinstance(geo_obj.solid_geometry, list) and \
+                    not isinstance(geo_obj.solid_geometry, MultiPolygon):
+                geo_obj.solid_geometry = [geo_obj.solid_geometry]
 
-        def follow_init(follow_obj, app_obj):
-            # Propagate options
-            follow_obj.options["cnctooldia"] = str(tooldia)
-            follow_obj.solid_geometry = self.grb_obj.follow_geometry
-            app_obj.inform.emit('[success] %s.' % _("Following geometry was generated"))
+            for g in geo_obj.solid_geometry:
+                if g:
+                    break
+                else:
+                    empty_cnt += 1
 
-        # in the end toggle the visibility of the origin object so we can see the generated Geometry
-        followed_obj.ui.plot_cb.set_value(False)
-        follow_name = outname
+            if empty_cnt == len(geo_obj.solid_geometry):
+                app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Empty Geometry in"), geo_obj.options["name"]))
+                return 'fail'
+            else:
+                app_obj.inform.emit('[success] %s: %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
 
-        for tool in self.iso_tools:
-            tooldia = self.iso_tools[tool]['tooldia']
-            new_name = "%s_%.*f" % (follow_name, self.decimals, tooldia)
+        self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
 
-            follow_state = self.iso_tools[tool]['data']['tools_iso_follow']
-            if follow_state:
-                ret = self.app.app_obj.new_object("geometry", new_name, follow_init)
-                if ret == 'fail':
-                    self.app.inform.emit("[ERROR_NOTCL] %s: %.*f" % (
-                        _("Failed to create Follow Geometry with tool diameter"), self.decimals, tooldia))
-                else:
-                    self.app.inform.emit("[success] %s: %.*f" % (
-                        _("Follow Geometry was created with tool diameter"), self.decimals, tooldia))
+        # the tools are finished but the isolation is not finished therefore it failed
+        if work_geo:
+            self.app.inform.emit("[WARNING] %s" % _("Partial failure. The geometry was processed with all tools.\n"
+                                                    "But there are still not-isolated geometry elements. "
+                                                    "Try to include a tool with smaller diameter."))
+            msg = _("The following are coordinates for the copper features that could not be isolated:")
+            self.app.inform_shell.emit(msg)
+            msg = ''
+            for geo in work_geo:
+                pt = geo.representative_point()
+                coords = '(%s, %s), ' % (str(pt.x), str(pt.y))
+                msg += coords
+            self.app.inform_shell.emit(msg=msg)
 
-    def isolate_handler(self, isolated_obj):
+    def combined_normal(self, iso_obj, iso2geo, tools_storage, lim_area, negative_dia=None, plot=True):
         """
-        Creates a geometry object with paths around the gerber features.
 
-        :param isolated_obj:    Gerber object for which to generate the isolating routing geometry
-        :type isolated_obj:     AppObjects.FlatCAMGerber.GerberObject
-        :return: None
+        :param iso_obj:         the isolated Gerber object
+        :type iso_obj:          AppObjects.FlatCAMGerber.GerberObject
+        :param iso2geo:         specific geometry to isolate
+        :type iso2geo:          list of Shapely Polygon
+        :param tools_storage:   a dictionary that holds the tools and geometry
+        :type tools_storage:    dict
+        :param lim_area:        if not None restrict isolation to this area
+        :type lim_area:         Shapely Polygon or a list of them
+        :param negative_dia:    isolate the geometry with a negative value for the tool diameter
+        :type negative_dia:     bool
+        :param plot:            if to plot the resulting geometry object
+        :type plot:             bool
+        :return:                Isolated solid geometry
+        :rtype:
         """
-        selection = self.select_combo.get_value()
+        log.debug("ToolIsolation.combined_normal()")
 
-        if selection == _("All"):
-            self.isolate(isolated_obj=isolated_obj)
-        elif selection == _("Area Selection"):
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
+        total_solid_geometry = []
 
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.app.mp)
-                self.app.plotcanvas.graph_event_disconnect(self.app.mm)
-                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+        iso_name = iso_obj.options["name"] + '_iso_combined'
+        geometry = iso2geo
+        prog_plot = self.app.defaults["tools_iso_plotting"]
 
-            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
-            self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
-            self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
+        for tool in tools_storage:
+            tool_dia = tools_storage[tool]['tooldia']
+            tool_type = tools_storage[tool]['tool_type']
+            tool_data = tools_storage[tool]['data']
 
-            # disconnect flags
-            self.area_sel_disconnect_flag = True
+            to_follow = tool_data['tools_iso_follow']
 
-        elif selection == _("Polygon Selection"):
-            # disengage the grid snapping since it may be hard to click on polygons with grid snapping on
-            if self.app.ui.grid_snap_btn.isChecked():
-                self.grid_status_memory = True
-                self.app.ui.grid_snap_btn.trigger()
-            else:
-                self.grid_status_memory = False
+            # TODO what to do when the iso2geo param is not None but the Follow cb is checked
+            # for the case when limited area is used .... the follow geo should be clipped too
+            work_geo = geometry
+            if work_geo is None:
+                work_geo = iso_obj.follow_geometry if to_follow else iso_obj.solid_geometry
 
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
-            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_poly_mouse_click_release)
-            self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
+            iso_t = {
+                'ext': 0,
+                'int': 1,
+                'full': 2
+            }[tool_data['tools_iso_isotype']]
 
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_release',
-                                                           self.app.on_mouse_click_release_over_plot)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+            passes = tool_data['tools_iso_passes']
+            overlap = tool_data['tools_iso_overlap']
+            overlap /= 100.0
 
-            # disconnect flags
-            self.poly_sel_disconnect_flag = True
+            milling_type = tool_data['tools_iso_milling_type']
 
-        elif selection == _("Reference Object"):
-            ref_obj = self.app.collection.get_by_name(self.reference_combo.get_value())
-            ref_geo = cascaded_union(ref_obj.solid_geometry)
-            use_geo = cascaded_union(isolated_obj.solid_geometry).difference(ref_geo)
-            self.isolate(isolated_obj=isolated_obj, geometry=use_geo)
+            iso_except = self.ui.except_cb.get_value()
 
-    def isolate(self, isolated_obj, geometry=None, limited_area=None, negative_dia=None, plot=True):
-        """
-        Creates an isolation routing geometry object in the project.
+            outname = "%s_%.*f" % (iso_obj.options["name"], self.decimals, float(tool_dia))
 
-        :param isolated_obj:    Gerber object for which to generate the isolating routing geometry
-        :type isolated_obj:     AppObjects.FlatCAMGerber.GerberObject
-        :param geometry:        specific geometry to isolate
-        :type geometry:         List of Shapely polygon
-        :param limited_area:    if not None isolate only this area
-        :type limited_area:     Shapely Polygon or a list of them
-        :param negative_dia:    isolate the geometry with a negative value for the tool diameter
-        :type negative_dia:     bool
-        :param plot:            if to plot the resulting geometry object
-        :type plot:             bool
-        :return: None
-        """
+            internal_name = outname + "_iso"
+            if iso_t == 0:
+                internal_name = outname + "_ext_iso"
+            elif iso_t == 1:
+                internal_name = outname + "_int_iso"
 
-        combine = self.combine_passes_cb.get_value()
-        tools_storage = self.iso_tools
+            tool_data.update({
+                "name": internal_name,
+            })
 
-        # update the Common Parameters valuse in the self.iso_tools
-        for tool_iso in self.iso_tools:
-            for key in self.iso_tools[tool_iso]:
-                if key == 'data':
-                    self.iso_tools[tool_iso][key]["tools_iso_rest"] = self.rest_cb.get_value()
-                    self.iso_tools[tool_iso][key]["tools_iso_combine_passes"] = combine
-                    self.iso_tools[tool_iso][key]["tools_iso_isoexcept"] = self.except_cb.get_value()
-                    self.iso_tools[tool_iso][key]["tools_iso_selection"] = self.select_combo.get_value()
-                    self.iso_tools[tool_iso][key]["tools_iso_area_shape"] = self.area_shape_radio.get_value()
+            solid_geo = []
+            for nr_pass in range(passes):
+                iso_offset = tool_dia * ((2 * nr_pass + 1) / 2.0000001) - (nr_pass * overlap * tool_dia)
+                if negative_dia:
+                    iso_offset = -iso_offset
 
-        if combine:
-            if self.rest_cb.get_value():
-                self.combined_rest(iso_obj=isolated_obj, iso2geo=geometry, tools_storage=tools_storage,
-                                   lim_area=limited_area, negative_dia=negative_dia, plot=plot)
-            else:
-                self.combined_normal(iso_obj=isolated_obj, iso2geo=geometry, tools_storage=tools_storage,
-                                     lim_area=limited_area, negative_dia=negative_dia, plot=plot)
+                # if milling type is climb then the move is counter-clockwise around features
+                mill_dir = 1 if milling_type == 'cl' else 0
 
-        else:
-            prog_plot = self.app.defaults["tools_iso_plotting"]
+                iso_geo = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
+                                                 follow=to_follow, nr_passes=nr_pass, prog_plot=prog_plot)
+                if iso_geo == 'fail':
+                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
+                    continue
+                try:
+                    for geo in iso_geo:
+                        solid_geo.append(geo)
+                except TypeError:
+                    solid_geo.append(iso_geo)
 
-            for tool in tools_storage:
-                tool_data = tools_storage[tool]['data']
-                to_follow = tool_data['tools_iso_follow']
+            # ############################################################
+            # ########## AREA SUBTRACTION ################################
+            # ############################################################
+            if iso_except:
+                self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                solid_geo = self.area_subtraction(solid_geo)
 
-                work_geo = geometry
-                if work_geo is None:
-                    work_geo = isolated_obj.follow_geometry if to_follow else isolated_obj.solid_geometry
+            if lim_area:
+                self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
+                solid_geo = self.area_intersection(solid_geo, intersection_geo=lim_area)
 
-                iso_t = {
-                    'ext': 0,
-                    'int': 1,
-                    'full': 2
-                }[tool_data['tools_iso_isotype']]
+            # make sure that no empty geometry element is in the solid_geometry
+            new_solid_geo = [geo for geo in solid_geo if not geo.is_empty]
 
-                passes = tool_data['tools_iso_passes']
-                overlap = tool_data['tools_iso_overlap']
-                overlap /= 100.0
+            tools_storage.update({
+                tool: {
+                    'tooldia':          float(tool_dia),
+                    'offset':           'Path',
+                    'offset_value':     0.0,
+                    'type':             _('Rough'),
+                    'tool_type':        tool_type,
+                    'data':             tool_data,
+                    'solid_geometry':   deepcopy(new_solid_geo)
+                }
+            })
 
-                milling_type = tool_data['tools_iso_milling_type']
+            total_solid_geometry += new_solid_geo
 
-                iso_except = self.except_cb.get_value()
+        # clean the progressive plotted shapes if it was used
+        if prog_plot == 'progressive':
+            self.temp_shapes.clear(update=True)
 
-                for i in range(passes):
-                    tool_dia = tools_storage[tool]['tooldia']
-                    tool_type = tools_storage[tool]['tool_type']
+        def iso_init(geo_obj, app_obj):
+            geo_obj.options["cnctooldia"] = str(tool_dia)
 
-                    iso_offset = tool_dia * ((2 * i + 1) / 2.0000001) - (i * overlap * tool_dia)
-                    if negative_dia:
-                        iso_offset = -iso_offset
+            geo_obj.tools = dict(tools_storage)
+            geo_obj.solid_geometry = total_solid_geometry
+            # even if combine is checked, one pass is still single-geo
 
-                    outname = "%s_%.*f" % (isolated_obj.options["name"], self.decimals, float(tool_dia))
+            if len(tools_storage) > 1:
+                geo_obj.multigeo = True
+            else:
+                if to_follow:
+                    geo_obj.multigeo = False
+                else:
+                    passes_no = 1
+                    for ky in tools_storage.keys():
+                        passes_no = float(tools_storage[ky]['data']['tools_iso_passes'])
+                        geo_obj.multigeo = True if passes_no > 1 else False
+                        break
+                    geo_obj.multigeo = True if passes_no > 1 else False
 
-                    if passes > 1:
-                        iso_name = outname + "_iso" + str(i + 1)
-                        if iso_t == 0:
-                            iso_name = outname + "_ext_iso" + str(i + 1)
-                        elif iso_t == 1:
-                            iso_name = outname + "_int_iso" + str(i + 1)
-                    else:
-                        iso_name = outname + "_iso"
-                        if iso_t == 0:
-                            iso_name = outname + "_ext_iso"
-                        elif iso_t == 1:
-                            iso_name = outname + "_int_iso"
+            # detect if solid_geometry is empty and this require list flattening which is "heavy"
+            # or just looking in the lists (they are one level depth) and if any is not empty
+            # proceed with object creation, if there are empty and the number of them is the length
+            # of the list then we have an empty solid_geometry which should raise a Custom Exception
+            empty_cnt = 0
+            if not isinstance(geo_obj.solid_geometry, list) and \
+                    not isinstance(geo_obj.solid_geometry, MultiPolygon):
+                geo_obj.solid_geometry = [geo_obj.solid_geometry]
 
-                    # if milling type is climb then the move is counter-clockwise around features
-                    mill_dir = 1 if milling_type == 'cl' else 0
+            for g in geo_obj.solid_geometry:
+                if g:
+                    break
+                else:
+                    empty_cnt += 1
 
-                    iso_geo = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
-                                                     follow=to_follow, nr_passes=i, prog_plot=prog_plot)
-                    if iso_geo == 'fail':
-                        self.app.inform.emit(
-                            '[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
-                        continue
+            if empty_cnt == len(geo_obj.solid_geometry):
+                app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Empty Geometry in"), geo_obj.options["name"]))
+                return 'fail'
+            else:
+                app_obj.inform.emit('[success] %s: %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
 
-                    # ############################################################
-                    # ########## AREA SUBTRACTION ################################
-                    # ############################################################
-                    if iso_except:
-                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                        iso_geo = self.area_subtraction(iso_geo)
+        self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
 
-                    if limited_area:
-                        self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
-                        iso_geo = self.area_intersection(iso_geo, intersection_geo=limited_area)
+    def area_subtraction(self, geo, subtraction_geo=None):
+        """
+        Subtracts the subtraction_geo (if present else self.solid_geometry) from the geo
 
-                    # make sure that no empty geometry element is in the solid_geometry
-                    new_solid_geo = [geo for geo in iso_geo if not geo.is_empty]
+        :param geo:                 target geometry from which to subtract
+        :param subtraction_geo:     geometry that acts as subtraction geo
+        :return:
+        """
+        new_geometry = []
+        target_geo = geo
 
-                    tool_data.update({
-                        "name": iso_name,
-                    })
+        if subtraction_geo:
+            sub_union = cascaded_union(subtraction_geo)
+        else:
+            name = self.ui.exc_obj_combo.currentText()
+            subtractor_obj = self.app.collection.get_by_name(name)
+            sub_union = cascaded_union(subtractor_obj.solid_geometry)
 
-                    def iso_init(geo_obj, fc_obj):
-                        # Propagate options
-                        geo_obj.options["cnctooldia"] = str(tool_dia)
-                        geo_obj.solid_geometry = deepcopy(new_solid_geo)
+        try:
+            for geo_elem in target_geo:
+                if isinstance(geo_elem, Polygon):
+                    for ring in self.poly2rings(geo_elem):
+                        new_geo = ring.difference(sub_union)
+                        if new_geo and not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+                elif isinstance(geo_elem, MultiPolygon):
+                    for poly in geo_elem:
+                        for ring in self.poly2rings(poly):
+                            new_geo = ring.difference(sub_union)
+                            if new_geo and not new_geo.is_empty:
+                                new_geometry.append(new_geo)
+                elif isinstance(geo_elem, LineString) or isinstance(geo_elem, LinearRing):
+                    new_geo = geo_elem.difference(sub_union)
+                    if new_geo:
+                        if not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+                elif isinstance(geo_elem, MultiLineString):
+                    for line_elem in geo_elem:
+                        new_geo = line_elem.difference(sub_union)
+                        if new_geo and not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+        except TypeError:
+            if isinstance(target_geo, Polygon):
+                for ring in self.poly2rings(target_geo):
+                    new_geo = ring.difference(sub_union)
+                    if new_geo:
+                        if not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+            elif isinstance(target_geo, LineString) or isinstance(target_geo, LinearRing):
+                new_geo = target_geo.difference(sub_union)
+                if new_geo and not new_geo.is_empty:
+                    new_geometry.append(new_geo)
+            elif isinstance(target_geo, MultiLineString):
+                for line_elem in target_geo:
+                    new_geo = line_elem.difference(sub_union)
+                    if new_geo and not new_geo.is_empty:
+                        new_geometry.append(new_geo)
+        return new_geometry
 
-                        # ############################################################
-                        # ########## AREA SUBTRACTION ################################
-                        # ############################################################
-                        if self.except_cb.get_value():
-                            self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                            geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
+    def area_intersection(self, geo, intersection_geo=None):
+        """
+        Return the intersection geometry between geo and intersection_geo
 
-                        geo_obj.tools = {}
-                        geo_obj.tools['1'] = {}
-                        geo_obj.tools.update({
-                            '1': {
-                                'tooldia': float(tool_dia),
-                                'offset': 'Path',
-                                'offset_value': 0.0,
-                                'type': _('Rough'),
-                                'tool_type': tool_type,
-                                'data': tool_data,
-                                'solid_geometry': geo_obj.solid_geometry
-                            }
-                        })
+        :param geo:                 target geometry
+        :param intersection_geo:    second geometry
+        :return:
+        """
+        new_geometry = []
+        target_geo = geo
 
-                        # detect if solid_geometry is empty and this require list flattening which is "heavy"
-                        # or just looking in the lists (they are one level depth) and if any is not empty
-                        # proceed with object creation, if there are empty and the number of them is the length
-                        # of the list then we have an empty solid_geometry which should raise a Custom Exception
-                        empty_cnt = 0
-                        if not isinstance(geo_obj.solid_geometry, list):
-                            geo_obj.solid_geometry = [geo_obj.solid_geometry]
+        intersect_union = cascaded_union(intersection_geo)
 
-                        for g in geo_obj.solid_geometry:
-                            if g:
-                                break
-                            else:
-                                empty_cnt += 1
-
-                        if empty_cnt == len(geo_obj.solid_geometry):
-                            fc_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (
-                                _("Empty Geometry in"), geo_obj.options["name"]))
-                            return 'fail'
-                        else:
-                            fc_obj.inform.emit('[success] %s: %s' %
-                                               (_("Isolation geometry created"), geo_obj.options["name"]))
-                        geo_obj.multigeo = False
+        try:
+            for geo_elem in target_geo:
+                if isinstance(geo_elem, Polygon):
+                    for ring in self.poly2rings(geo_elem):
+                        new_geo = ring.intersection(intersect_union)
+                        if new_geo and not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+                elif isinstance(geo_elem, MultiPolygon):
+                    for poly in geo_elem:
+                        for ring in self.poly2rings(poly):
+                            new_geo = ring.intersection(intersect_union)
+                            if new_geo and not new_geo.is_empty:
+                                new_geometry.append(new_geo)
+                elif isinstance(geo_elem, LineString) or isinstance(geo_elem, LinearRing):
+                    new_geo = geo_elem.intersection(intersect_union)
+                    if new_geo:
+                        if not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+                elif isinstance(geo_elem, MultiLineString):
+                    for line_elem in geo_elem:
+                        new_geo = line_elem.intersection(intersect_union)
+                        if new_geo and not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+        except TypeError:
+            if isinstance(target_geo, Polygon):
+                for ring in self.poly2rings(target_geo):
+                    new_geo = ring.intersection(intersect_union)
+                    if new_geo:
+                        if not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+            elif isinstance(target_geo, LineString) or isinstance(target_geo, LinearRing):
+                new_geo = target_geo.intersection(intersect_union)
+                if new_geo and not new_geo.is_empty:
+                    new_geometry.append(new_geo)
+            elif isinstance(target_geo, MultiLineString):
+                for line_elem in target_geo:
+                    new_geo = line_elem.intersection(intersect_union)
+                    if new_geo and not new_geo.is_empty:
+                        new_geometry.append(new_geo)
+        return new_geometry
 
-                    self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
+    def on_poly_mouse_click_release(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            right_button = 2
+            self.app.event_is_dragging = self.app.event_is_dragging
+        else:
+            event_pos = (event.xdata, event.ydata)
+            right_button = 3
+            self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
 
-            # clean the progressive plotted shapes if it was used
+        try:
+            x = float(event_pos[0])
+            y = float(event_pos[1])
+        except TypeError:
+            return
 
-            if prog_plot == 'progressive':
-                self.temp_shapes.clear(update=True)
+        event_pos = (x, y)
+        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+        if self.app.grid_status():
+            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+        else:
+            curr_pos = (curr_pos[0], curr_pos[1])
 
-        # Switch notebook to Selected page
-        self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+        if event.button == 1:
+            if self.ui.poly_int_cb.get_value() is True:
+                clicked_poly = self.find_polygon_ignore_interiors(point=(curr_pos[0], curr_pos[1]),
+                                                                  geoset=self.grb_obj.solid_geometry)
 
-    def combined_rest(self, iso_obj, iso2geo, tools_storage, lim_area, negative_dia=None, plot=True):
-        """
-        Isolate the provided Gerber object using "rest machining" strategy
+                clicked_poly = self.get_selected_interior(clicked_poly, point=(curr_pos[0], curr_pos[1]))
 
-        :param iso_obj:         the isolated Gerber object
-        :type iso_obj:          AppObjects.FlatCAMGerber.GerberObject
-        :param iso2geo:         specific geometry to isolate
-        :type iso2geo:          list of Shapely Polygon
-        :param tools_storage:   a dictionary that holds the tools and geometry
-        :type tools_storage:    dict
-        :param lim_area:        if not None restrict isolation to this area
-        :type lim_area:         Shapely Polygon or a list of them
-        :param negative_dia:    isolate the geometry with a negative value for the tool diameter
-        :type negative_dia:     bool
-        :param plot:            if to plot the resulting geometry object
-        :type plot:             bool
-        :return:                Isolated solid geometry
-        :rtype:
-        """
+            else:
+                clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]), geoset=self.grb_obj.solid_geometry)
 
-        log.debug("ToolIsolation.combine_rest()")
+            if self.app.selection_type is not None:
+                self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
+                self.app.selection_type = None
+            elif clicked_poly:
+                if clicked_poly not in self.poly_dict.values():
+                    shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
+                                                        color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                        face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                        visible=True)
+                    self.poly_dict[shape_id] = clicked_poly
+                    self.app.inform.emit(
+                        '%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)),
+                                        _("Click to add next polygon or right click to start isolation."))
+                    )
+                else:
+                    try:
+                        for k, v in list(self.poly_dict.items()):
+                            if v == clicked_poly:
+                                self.app.tool_shapes.remove(k)
+                                self.poly_dict.pop(k)
+                                break
+                    except TypeError:
+                        return
+                    self.app.inform.emit(
+                        '%s. %s' % (_("Removed polygon"),
+                                    _("Click to add/remove next polygon or right click to start isolation."))
+                    )
 
-        total_solid_geometry = []
+                self.app.tool_shapes.redraw()
+            else:
+                self.app.inform.emit(_("No polygon detected under click position."))
+        elif event.button == right_button and self.app.event_is_dragging is False:
+            # restore the Grid snapping if it was active before
+            if self.grid_status_memory is True:
+                self.app.ui.grid_snap_btn.trigger()
 
-        iso_name = iso_obj.options["name"] + '_iso_rest'
-        work_geo = iso_obj.solid_geometry if iso2geo is None else iso2geo
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_poly_mouse_click_release)
+                self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.kp)
 
-        sorted_tools = []
-        for k, v in self.iso_tools.items():
-            sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
 
-        order = self.order_radio.get_value()
-        if order == 'fwd':
-            sorted_tools.sort(reverse=False)
-        elif order == 'rev':
-            sorted_tools.sort(reverse=True)
-        else:
-            pass
+            # disconnect flags
+            self.poly_sel_disconnect_flag = False
 
-        # decide to use "progressive" or "normal" plotting
-        prog_plot = self.app.defaults["tools_iso_plotting"]
+            self.app.tool_shapes.clear(update=True)
 
-        for sorted_tool in sorted_tools:
-            for tool in tools_storage:
-                if float('%.*f' % (self.decimals, tools_storage[tool]['tooldia'])) == sorted_tool:
+            if self.poly_dict:
+                poly_list = deepcopy(list(self.poly_dict.values()))
+                if self.ui.poly_int_cb.get_value() is True:
+                    # isolate the interior polygons with a negative tool
+                    self.isolate(isolated_obj=self.grb_obj, geometry=poly_list, negative_dia=True)
+                else:
+                    self.isolate(isolated_obj=self.grb_obj, geometry=poly_list)
+                self.poly_dict.clear()
+            else:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
 
-                    tool_dia = tools_storage[tool]['tooldia']
-                    tool_type = tools_storage[tool]['tool_type']
-                    tool_data = tools_storage[tool]['data']
+    def selection_area_handler(self, start_pos, end_pos, sel_type):
+        """
+        :param start_pos: mouse position when the selection LMB click was done
+        :param end_pos: mouse position when the left mouse button is released
+        :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
+        :return:
+        """
+        poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
 
-                    passes = tool_data['tools_iso_passes']
-                    overlap = tool_data['tools_iso_overlap']
-                    overlap /= 100.0
+        # delete previous selection shape
+        self.app.delete_selection_shape()
 
-                    milling_type = tool_data['tools_iso_milling_type']
-                    # if milling type is climb then the move is counter-clockwise around features
-                    mill_dir = True if milling_type == 'cl' else False
-                    iso_t = {
-                        'ext': 0,
-                        'int': 1,
-                        'full': 2
-                    }[tool_data['tools_iso_isotype']]
+        added_poly_count = 0
+        try:
+            for geo in self.solid_geometry:
+                if geo not in self.poly_dict.values():
+                    if sel_type is True:
+                        if geo.within(poly_selection):
+                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                                shape=geo,
+                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                                face_color=self.app.defaults[
+                                                                               'global_sel_draw_color'] + 'AF',
+                                                                visible=True)
+                            self.poly_dict[shape_id] = geo
+                            added_poly_count += 1
+                    else:
+                        if poly_selection.intersects(geo):
+                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                                shape=geo,
+                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                                face_color=self.app.defaults[
+                                                                               'global_sel_draw_color'] + 'AF',
+                                                                visible=True)
+                            self.poly_dict[shape_id] = geo
+                            added_poly_count += 1
+        except TypeError:
+            if self.solid_geometry not in self.poly_dict.values():
+                if sel_type is True:
+                    if self.solid_geometry.within(poly_selection):
+                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                            shape=self.solid_geometry,
+                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                            face_color=self.app.defaults[
+                                                                           'global_sel_draw_color'] + 'AF',
+                                                            visible=True)
+                        self.poly_dict[shape_id] = self.solid_geometry
+                        added_poly_count += 1
+                else:
+                    if poly_selection.intersects(self.solid_geometry):
+                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                            shape=self.solid_geometry,
+                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                            face_color=self.app.defaults[
+                                                                           'global_sel_draw_color'] + 'AF',
+                                                            visible=True)
+                        self.poly_dict[shape_id] = self.solid_geometry
+                        added_poly_count += 1
 
-                    forced_rest = self.forced_rest_iso_cb.get_value()
-                    iso_except = self.except_cb.get_value()
+        if added_poly_count > 0:
+            self.app.tool_shapes.redraw()
+            self.app.inform.emit(
+                '%s: %d. %s' % (_("Added polygon"),
+                                int(added_poly_count),
+                                _("Click to add next polygon or right click to start isolation."))
+            )
+        else:
+            self.app.inform.emit(_("No polygon in selection."))
 
-                    outname = "%s_%.*f" % (iso_obj.options["name"], self.decimals, float(tool_dia))
-                    internal_name = outname + "_iso"
-                    if iso_t == 0:
-                        internal_name = outname + "_ext_iso"
-                    elif iso_t == 1:
-                        internal_name = outname + "_int_iso"
+    # To be called after clicking on the plot.
+    def on_mouse_release(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            # event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            # event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
 
-                    tool_data.update({
-                        "name": internal_name,
-                    })
+        event_pos = self.app.plotcanvas.translate_coords(event_pos)
+        if self.app.grid_status():
+            curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
+        else:
+            curr_pos = (event_pos[0], event_pos[1])
 
-                    solid_geo, work_geo = self.generate_rest_geometry(geometry=work_geo, tooldia=tool_dia,
-                                                                      passes=passes, overlap=overlap, invert=mill_dir,
-                                                                      env_iso_type=iso_t, negative_dia=negative_dia,
-                                                                      forced_rest=forced_rest,
-                                                                      prog_plot=prog_plot,
-                                                                      prog_plot_handler=self.plot_temp_shapes)
+        x1, y1 = curr_pos[0], curr_pos[1]
 
-                    # ############################################################
-                    # ########## AREA SUBTRACTION ################################
-                    # ############################################################
-                    if iso_except:
-                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                        solid_geo = self.area_subtraction(solid_geo)
+        shape_type = self.area_shape_radio.get_value()
 
-                    if lim_area:
-                        self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
-                        solid_geo = self.area_intersection(solid_geo, intersection_geo=lim_area)
+        # do clear area only for left mouse clicks
+        if event.button == 1:
+            if shape_type == "square":
+                if self.first_click is False:
+                    self.first_click = True
+                    self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the paint area."))
 
-                    # make sure that no empty geometry element is in the solid_geometry
-                    new_solid_geo = [geo for geo in solid_geo if not geo.is_empty]
+                    self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
+                    if self.app.grid_status():
+                        self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
+                else:
+                    self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
+                    self.app.delete_selection_shape()
 
-                    tools_storage.update({
-                        tool: {
-                            'tooldia': float(tool_dia),
-                            'offset': 'Path',
-                            'offset_value': 0.0,
-                            'type': _('Rough'),
-                            'tool_type': tool_type,
-                            'data': tool_data,
-                            'solid_geometry': deepcopy(new_solid_geo)
-                        }
-                    })
+                    x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
 
-                    total_solid_geometry += new_solid_geo
+                    pt1 = (x0, y0)
+                    pt2 = (x1, y0)
+                    pt3 = (x1, y1)
+                    pt4 = (x0, y1)
 
-                    # if the geometry is all isolated
-                    if not work_geo:
-                        break
+                    new_rectangle = Polygon([pt1, pt2, pt3, pt4])
+                    self.sel_rect.append(new_rectangle)
 
-        # clean the progressive plotted shapes if it was used
-        if self.app.defaults["tools_iso_plotting"] == 'progressive':
-            self.temp_shapes.clear(update=True)
+                    # add a temporary shape on canvas
+                    self.draw_tool_selection_shape(old_coords=(x0, y0), coords=(x1, y1))
 
-        def iso_init(geo_obj, app_obj):
-            geo_obj.options["cnctooldia"] = str(tool_dia)
+                    self.first_click = False
+                    return
+            else:
+                self.points.append((x1, y1))
 
-            geo_obj.tools = dict(tools_storage)
-            geo_obj.solid_geometry = total_solid_geometry
-            # even if combine is checked, one pass is still single-geo
+                if len(self.points) > 1:
+                    self.poly_drawn = True
+                    self.app.inform.emit(_("Click on next Point or click right mouse button to complete ..."))
 
-            # remove the tools that have no geometry
-            for geo_tool in list(geo_obj.tools.keys()):
-                if not geo_obj.tools[geo_tool]['solid_geometry']:
-                    geo_obj.tools.pop(geo_tool, None)
+                return ""
+        elif event.button == right_button and self.mouse_is_dragging is False:
 
-            if len(tools_storage) > 1:
-                geo_obj.multigeo = True
+            shape_type = self.ui.area_shape_radio.get_value()
+
+            if shape_type == "square":
+                self.first_click = False
             else:
-                for ky in tools_storage.keys():
-                    passes_no = float(tools_storage[ky]['data']['tools_iso_passes'])
-                    geo_obj.multigeo = True if passes_no > 1 else False
-                    break
+                # if we finish to add a polygon
+                if self.poly_drawn is True:
+                    try:
+                        # try to add the point where we last clicked if it is not already in the self.points
+                        last_pt = (x1, y1)
+                        if last_pt != self.points[-1]:
+                            self.points.append(last_pt)
+                    except IndexError:
+                        pass
 
-            # detect if solid_geometry is empty and this require list flattening which is "heavy"
-            # or just looking in the lists (they are one level depth) and if any is not empty
-            # proceed with object creation, if there are empty and the number of them is the length
-            # of the list then we have an empty solid_geometry which should raise a Custom Exception
-            empty_cnt = 0
-            if not isinstance(geo_obj.solid_geometry, list) and \
-                    not isinstance(geo_obj.solid_geometry, MultiPolygon):
-                geo_obj.solid_geometry = [geo_obj.solid_geometry]
+                    # we need to add a Polygon and a Polygon can be made only from at least 3 points
+                    if len(self.points) > 2:
+                        self.delete_moving_selection_shape()
+                        pol = Polygon(self.points)
+                        # do not add invalid polygons even if they are drawn by utility geometry
+                        if pol.is_valid:
+                            self.sel_rect.append(pol)
+                            self.draw_selection_shape_polygon(points=self.points)
+                            self.app.inform.emit(
+                                _("Zone added. Click to start adding next zone or right click to finish."))
 
-            for g in geo_obj.solid_geometry:
-                if g:
-                    break
-                else:
-                    empty_cnt += 1
+                    self.points = []
+                    self.poly_drawn = False
+                    return
 
-            if empty_cnt == len(geo_obj.solid_geometry):
-                app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Empty Geometry in"), geo_obj.options["name"]))
-                return 'fail'
+            self.delete_tool_selection_shape()
+
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+                self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
             else:
-                app_obj.inform.emit('[success] %s: %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.kp)
 
-        self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+                                                                  self.app.on_mouse_click_over_plot)
+            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
+                                                                  self.app.on_mouse_move_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
 
-        # the tools are finished but the isolation is not finished therefore it failed
-        if work_geo:
-            self.app.inform.emit("[WARNING] %s" % _("Partial failure. The geometry was processed with all tools.\n"
-                                                    "But there are still not-isolated geometry elements. "
-                                                    "Try to include a tool with smaller diameter."))
-            msg = _("The following are coordinates for the copper features that could not be isolated:")
-            self.app.inform_shell.emit(msg)
-            msg = ''
-            for geo in work_geo:
-                pt = geo.representative_point()
-                coords = '(%s, %s), ' % (str(pt.x), str(pt.y))
-                msg += coords
-            self.app.inform_shell.emit(msg=msg)
+            # disconnect flags
+            self.area_sel_disconnect_flag = False
 
-    def combined_normal(self, iso_obj, iso2geo, tools_storage, lim_area, negative_dia=None, plot=True):
-        """
+            if len(self.sel_rect) == 0:
+                return
 
-        :param iso_obj:         the isolated Gerber object
-        :type iso_obj:          AppObjects.FlatCAMGerber.GerberObject
-        :param iso2geo:         specific geometry to isolate
-        :type iso2geo:          list of Shapely Polygon
-        :param tools_storage:   a dictionary that holds the tools and geometry
-        :type tools_storage:    dict
-        :param lim_area:        if not None restrict isolation to this area
-        :type lim_area:         Shapely Polygon or a list of them
-        :param negative_dia:    isolate the geometry with a negative value for the tool diameter
-        :type negative_dia:     bool
-        :param plot:            if to plot the resulting geometry object
-        :type plot:             bool
-        :return:                Isolated solid geometry
-        :rtype:
-        """
-        log.debug("ToolIsolation.combined_normal()")
+            self.sel_rect = cascaded_union(self.sel_rect)
+            self.isolate(isolated_obj=self.grb_obj, limited_area=self.sel_rect, plot=True)
+            self.sel_rect = []
 
-        total_solid_geometry = []
+    # called on mouse move
+    def on_mouse_move(self, event):
+        shape_type = self.ui.area_shape_radio.get_value()
 
-        iso_name = iso_obj.options["name"] + '_iso_combined'
-        geometry = iso2geo
-        prog_plot = self.app.defaults["tools_iso_plotting"]
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            # right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            # right_button = 3
 
-        for tool in tools_storage:
-            tool_dia = tools_storage[tool]['tooldia']
-            tool_type = tools_storage[tool]['tool_type']
-            tool_data = tools_storage[tool]['data']
+        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
 
-            to_follow = tool_data['tools_iso_follow']
+        # detect mouse dragging motion
+        if event_is_dragging is True:
+            self.mouse_is_dragging = True
+        else:
+            self.mouse_is_dragging = False
 
-            # TODO what to do when the iso2geo param is not None but the Follow cb is checked
-            # for the case when limited area is used .... the follow geo should be clipped too
-            work_geo = geometry
-            if work_geo is None:
-                work_geo = iso_obj.follow_geometry if to_follow else iso_obj.solid_geometry
+        # update the cursor position
+        if self.app.grid_status():
+            # Update cursor
+            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
 
-            iso_t = {
-                'ext': 0,
-                'int': 1,
-                'full': 2
-            }[tool_data['tools_iso_isotype']]
+            self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
+                                         symbol='++', edge_color=self.app.cursor_color_3D,
+                                         edge_width=self.app.defaults["global_cursor_width"],
+                                         size=self.app.defaults["global_cursor_size"])
 
-            passes = tool_data['tools_iso_passes']
-            overlap = tool_data['tools_iso_overlap']
-            overlap /= 100.0
+        if self.cursor_pos is None:
+            self.cursor_pos = (0, 0)
 
-            milling_type = tool_data['tools_iso_milling_type']
+        self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
+        self.app.dy = curr_pos[1] - float(self.cursor_pos[1])
 
-            iso_except = self.except_cb.get_value()
+        # # update the positions on status bar
+        self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f&nbsp;" % (curr_pos[0], curr_pos[1]))
+        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
 
-            outname = "%s_%.*f" % (iso_obj.options["name"], self.decimals, float(tool_dia))
+        units = self.app.defaults["units"].lower()
+        self.app.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
 
-            internal_name = outname + "_iso"
-            if iso_t == 0:
-                internal_name = outname + "_ext_iso"
-            elif iso_t == 1:
-                internal_name = outname + "_int_iso"
+        # draw the utility geometry
+        if shape_type == "square":
+            if self.first_click:
+                self.app.delete_selection_shape()
+                self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
+                                                     coords=(curr_pos[0], curr_pos[1]))
+        else:
+            self.delete_moving_selection_shape()
+            self.draw_moving_selection_shape_poly(points=self.points, data=(curr_pos[0], curr_pos[1]))
 
-            tool_data.update({
-                "name": internal_name,
-            })
+    def on_key_press(self, event):
+        # modifiers = QtWidgets.QApplication.keyboardModifiers()
+        # matplotlib_key_flag = False
 
-            solid_geo = []
-            for nr_pass in range(passes):
-                iso_offset = tool_dia * ((2 * nr_pass + 1) / 2.0000001) - (nr_pass * overlap * tool_dia)
-                if negative_dia:
-                    iso_offset = -iso_offset
+        # events out of the self.app.collection view (it's about Project Tab) are of type int
+        if type(event) is int:
+            key = event
+        # events from the GUI are of type QKeyEvent
+        elif type(event) == QtGui.QKeyEvent:
+            key = event.key()
+        elif isinstance(event, mpl_key_event):  # MatPlotLib key events are trickier to interpret than the rest
+            # matplotlib_key_flag = True
 
-                # if milling type is climb then the move is counter-clockwise around features
-                mill_dir = 1 if milling_type == 'cl' else 0
+            key = event.key
+            key = QtGui.QKeySequence(key)
 
-                iso_geo = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
-                                                 follow=to_follow, nr_passes=nr_pass, prog_plot=prog_plot)
-                if iso_geo == 'fail':
-                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
-                    continue
-                try:
-                    for geo in iso_geo:
-                        solid_geo.append(geo)
-                except TypeError:
-                    solid_geo.append(iso_geo)
+            # check for modifiers
+            key_string = key.toString().lower()
+            if '+' in key_string:
+                mod, __, key_text = key_string.rpartition('+')
+                if mod.lower() == 'ctrl':
+                    # modifiers = QtCore.Qt.ControlModifier
+                    pass
+                elif mod.lower() == 'alt':
+                    # modifiers = QtCore.Qt.AltModifier
+                    pass
+                elif mod.lower() == 'shift':
+                    # modifiers = QtCore.Qt.ShiftModifier
+                    pass
+                else:
+                    # modifiers = QtCore.Qt.NoModifier
+                    pass
+                key = QtGui.QKeySequence(key_text)
 
-            # ############################################################
-            # ########## AREA SUBTRACTION ################################
-            # ############################################################
-            if iso_except:
-                self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                solid_geo = self.area_subtraction(solid_geo)
+        # events from Vispy are of type KeyEvent
+        else:
+            key = event.key
 
-            if lim_area:
-                self.app.proc_container.update_view_text(' %s' % _("Intersecting Geo"))
-                solid_geo = self.area_intersection(solid_geo, intersection_geo=lim_area)
+        if key == QtCore.Qt.Key_Escape or key == 'Escape':
 
-            # make sure that no empty geometry element is in the solid_geometry
-            new_solid_geo = [geo for geo in solid_geo if not geo.is_empty]
+            if self.area_sel_disconnect_flag is True:
+                if self.app.is_legacy is False:
+                    self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
+                    self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+                    self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
+                else:
+                    self.app.plotcanvas.graph_event_disconnect(self.mr)
+                    self.app.plotcanvas.graph_event_disconnect(self.mm)
+                    self.app.plotcanvas.graph_event_disconnect(self.kp)
 
-            tools_storage.update({
-                tool: {
-                    'tooldia': float(tool_dia),
-                    'offset': 'Path',
-                    'offset_value': 0.0,
-                    'type': _('Rough'),
-                    'tool_type': tool_type,
-                    'data': tool_data,
-                    'solid_geometry': deepcopy(new_solid_geo)
-                }
-            })
+                self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+                                                                      self.app.on_mouse_click_over_plot)
+                self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
+                                                                      self.app.on_mouse_move_over_plot)
+                self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                      self.app.on_mouse_click_release_over_plot)
 
-            total_solid_geometry += new_solid_geo
+            if self.poly_sel_disconnect_flag is False:
+                # restore the Grid snapping if it was active before
+                if self.grid_status_memory is True:
+                    self.app.ui.grid_snap_btn.trigger()
 
-        # clean the progressive plotted shapes if it was used
-        if prog_plot == 'progressive':
-            self.temp_shapes.clear(update=True)
+                if self.app.is_legacy is False:
+                    self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_poly_mouse_click_release)
+                    self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
+                else:
+                    self.app.plotcanvas.graph_event_disconnect(self.mr)
+                    self.app.plotcanvas.graph_event_disconnect(self.kp)
 
-        def iso_init(geo_obj, app_obj):
-            geo_obj.options["cnctooldia"] = str(tool_dia)
+                self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                      self.app.on_mouse_click_release_over_plot)
 
-            geo_obj.tools = dict(tools_storage)
-            geo_obj.solid_geometry = total_solid_geometry
-            # even if combine is checked, one pass is still single-geo
+            self.points = []
+            self.poly_drawn = False
+            self.delete_moving_selection_shape()
+            self.delete_tool_selection_shape()
 
-            if len(tools_storage) > 1:
-                geo_obj.multigeo = True
-            else:
-                if to_follow:
-                    geo_obj.multigeo = False
-                else:
-                    passes_no = 1
-                    for ky in tools_storage.keys():
-                        passes_no = float(tools_storage[ky]['data']['tools_iso_passes'])
-                        geo_obj.multigeo = True if passes_no > 1 else False
-                        break
-                    geo_obj.multigeo = True if passes_no > 1 else False
+    def on_iso_tool_add_from_db_executed(self, tool):
+        """
+        Here add the tool from DB  in the selected geometry object
+        :return:
+        """
+        tool_from_db = deepcopy(tool)
 
-            # detect if solid_geometry is empty and this require list flattening which is "heavy"
-            # or just looking in the lists (they are one level depth) and if any is not empty
-            # proceed with object creation, if there are empty and the number of them is the length
-            # of the list then we have an empty solid_geometry which should raise a Custom Exception
-            empty_cnt = 0
-            if not isinstance(geo_obj.solid_geometry, list) and \
-                    not isinstance(geo_obj.solid_geometry, MultiPolygon):
-                geo_obj.solid_geometry = [geo_obj.solid_geometry]
+        res = self.on_tool_from_db_inserted(tool=tool_from_db)
 
-            for g in geo_obj.solid_geometry:
-                if g:
-                    break
-                else:
-                    empty_cnt += 1
+        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)
 
-            if empty_cnt == len(geo_obj.solid_geometry):
-                app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Empty Geometry in"), geo_obj.options["name"]))
-                return 'fail'
-            else:
-                app_obj.inform.emit('[success] %s: %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
+        if res == 'fail':
+            return
+        self.app.inform.emit('[success] %s' % _("Tool from DB added in Tool Table."))
 
-        self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
+        # select last tool added
+        toolid = res
+        for row in range(self.ui.tools_table.rowCount()):
+            if int(self.ui.tools_table.item(row, 3).text()) == toolid:
+                self.ui.tools_table.selectRow(row)
+        self.on_row_selection_change()
 
-    def area_subtraction(self, geo, subtraction_geo=None):
+    def on_tool_from_db_inserted(self, tool):
         """
-        Subtracts the subtraction_geo (if present else self.solid_geometry) from the geo
-
-        :param geo:                 target geometry from which to subtract
-        :param subtraction_geo:     geometry that acts as subtraction geo
-        :return:
+        Called from the Tools DB object through a App method when adding a tool from Tools Database
+        :param tool: a dict with the tool data
+        :return: None
         """
-        new_geometry = []
-        target_geo = geo
 
-        if subtraction_geo:
-            sub_union = cascaded_union(subtraction_geo)
-        else:
-            name = self.exc_obj_combo.currentText()
-            subtractor_obj = self.app.collection.get_by_name(name)
-            sub_union = cascaded_union(subtractor_obj.solid_geometry)
+        self.ui_disconnect()
+        self.units = self.app.defaults['units'].upper()
 
-        try:
-            for geo_elem in target_geo:
-                if isinstance(geo_elem, Polygon):
-                    for ring in self.poly2rings(geo_elem):
-                        new_geo = ring.difference(sub_union)
-                        if new_geo and not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-                elif isinstance(geo_elem, MultiPolygon):
-                    for poly in geo_elem:
-                        for ring in self.poly2rings(poly):
-                            new_geo = ring.difference(sub_union)
-                            if new_geo and not new_geo.is_empty:
-                                new_geometry.append(new_geo)
-                elif isinstance(geo_elem, LineString) or isinstance(geo_elem, LinearRing):
-                    new_geo = geo_elem.difference(sub_union)
-                    if new_geo:
-                        if not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-                elif isinstance(geo_elem, MultiLineString):
-                    for line_elem in geo_elem:
-                        new_geo = line_elem.difference(sub_union)
-                        if new_geo and not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-        except TypeError:
-            if isinstance(target_geo, Polygon):
-                for ring in self.poly2rings(target_geo):
-                    new_geo = ring.difference(sub_union)
-                    if new_geo:
-                        if not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-            elif isinstance(target_geo, LineString) or isinstance(target_geo, LinearRing):
-                new_geo = target_geo.difference(sub_union)
-                if new_geo and not new_geo.is_empty:
-                    new_geometry.append(new_geo)
-            elif isinstance(target_geo, MultiLineString):
-                for line_elem in target_geo:
-                    new_geo = line_elem.difference(sub_union)
-                    if new_geo and not new_geo.is_empty:
-                        new_geometry.append(new_geo)
-        return new_geometry
+        tooldia = float(tool['tooldia'])
 
-    def area_intersection(self, geo, intersection_geo=None):
-        """
-        Return the intersection geometry between geo and intersection_geo
+        # construct a list of all 'tooluid' in the self.tools
+        tool_uid_list = []
+        for tooluid_key in self.iso_tools:
+            tool_uid_item = int(tooluid_key)
+            tool_uid_list.append(tool_uid_item)
 
-        :param geo:                 target geometry
-        :param intersection_geo:    second geometry
-        :return:
-        """
-        new_geometry = []
-        target_geo = geo
+        # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
+        if not tool_uid_list:
+            max_uid = 0
+        else:
+            max_uid = max(tool_uid_list)
+        tooluid = max_uid + 1
 
-        intersect_union = cascaded_union(intersection_geo)
+        tooldia = float('%.*f' % (self.decimals, tooldia))
 
-        try:
-            for geo_elem in target_geo:
-                if isinstance(geo_elem, Polygon):
-                    for ring in self.poly2rings(geo_elem):
-                        new_geo = ring.intersection(intersect_union)
-                        if new_geo and not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-                elif isinstance(geo_elem, MultiPolygon):
-                    for poly in geo_elem:
-                        for ring in self.poly2rings(poly):
-                            new_geo = ring.intersection(intersect_union)
-                            if new_geo and not new_geo.is_empty:
-                                new_geometry.append(new_geo)
-                elif isinstance(geo_elem, LineString) or isinstance(geo_elem, LinearRing):
-                    new_geo = geo_elem.intersection(intersect_union)
-                    if new_geo:
-                        if not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-                elif isinstance(geo_elem, MultiLineString):
-                    for line_elem in geo_elem:
-                        new_geo = line_elem.intersection(intersect_union)
-                        if new_geo and not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-        except TypeError:
-            if isinstance(target_geo, Polygon):
-                for ring in self.poly2rings(target_geo):
-                    new_geo = ring.intersection(intersect_union)
-                    if new_geo:
-                        if not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-            elif isinstance(target_geo, LineString) or isinstance(target_geo, LinearRing):
-                new_geo = target_geo.intersection(intersect_union)
-                if new_geo and not new_geo.is_empty:
-                    new_geometry.append(new_geo)
-            elif isinstance(target_geo, MultiLineString):
-                for line_elem in target_geo:
-                    new_geo = line_elem.intersection(intersect_union)
-                    if new_geo and not new_geo.is_empty:
-                        new_geometry.append(new_geo)
-        return new_geometry
+        tool_dias = []
+        for k, v in self.iso_tools.items():
+            for tool_v in v.keys():
+                if tool_v == 'tooldia':
+                    tool_dias.append(float('%.*f' % (self.decimals, (v[tool_v]))))
 
-    def on_poly_mouse_click_release(self, event):
-        if self.app.is_legacy is False:
-            event_pos = event.pos
-            right_button = 2
-            self.app.event_is_dragging = self.app.event_is_dragging
-        else:
-            event_pos = (event.xdata, event.ydata)
-            right_button = 3
-            self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
+        if float('%.*f' % (self.decimals, tooldia)) in tool_dias:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. Tool already in Tool Table."))
+            self.ui_connect()
+            return 'fail'
 
-        try:
-            x = float(event_pos[0])
-            y = float(event_pos[1])
-        except TypeError:
-            return
+        self.iso_tools.update({
+            tooluid: {
+                'tooldia':          float('%.*f' % (self.decimals, tooldia)),
+                'offset':           tool['offset'],
+                'offset_value':     tool['offset_value'],
+                'type':             tool['type'],
+                'tool_type':        tool['tool_type'],
+                'data':             deepcopy(tool['data']),
+                'solid_geometry':   []
+            }
+        })
 
-        event_pos = (x, y)
-        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
-        if self.app.grid_status():
-            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
-        else:
-            curr_pos = (curr_pos[0], curr_pos[1])
+        self.iso_tools[tooluid]['data']['name'] = '_iso'
 
-        if event.button == 1:
-            if self.poly_int_cb.get_value() is True:
-                clicked_poly = self.find_polygon_ignore_interiors(point=(curr_pos[0], curr_pos[1]),
-                                                                  geoset=self.grb_obj.solid_geometry)
+        self.app.inform.emit('[success] %s' % _("New tool added to Tool Table."))
 
-                clicked_poly = self.get_selected_interior(clicked_poly, point=(curr_pos[0], curr_pos[1]))
+        self.ui_connect()
+        self.build_ui()
 
-            else:
-                clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]), geoset=self.grb_obj.solid_geometry)
+        # select the tool just added
+        for row in range(self.ui.tools_table.rowCount()):
+            if int(self.ui.tools_table.item(row, 3).text()) == self.tooluid:
+                self.ui.tools_table.selectRow(row)
+                break
 
-            if self.app.selection_type is not None:
-                self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
-                self.app.selection_type = None
-            elif clicked_poly:
-                if clicked_poly not in self.poly_dict.values():
-                    shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
-                                                        color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                        face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                        visible=True)
-                    self.poly_dict[shape_id] = clicked_poly
-                    self.app.inform.emit(
-                        '%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)),
-                                        _("Click to add next polygon or right click to start isolation."))
-                    )
-                else:
-                    try:
-                        for k, v in list(self.poly_dict.items()):
-                            if v == clicked_poly:
-                                self.app.tool_shapes.remove(k)
-                                self.poly_dict.pop(k)
-                                break
-                    except TypeError:
-                        return
-                    self.app.inform.emit(
-                        '%s. %s' % (_("Removed polygon"),
-                                    _("Click to add/remove next polygon or right click to start isolation."))
-                    )
+        # if self.ui.tools_table.rowCount() != 0:
+        #     self.param_frame.setDisabled(False)
 
-                self.app.tool_shapes.redraw()
-            else:
-                self.app.inform.emit(_("No polygon detected under click position."))
-        elif event.button == right_button and self.app.event_is_dragging is False:
-            # restore the Grid snapping if it was active before
-            if self.grid_status_memory is True:
-                self.app.ui.grid_snap_btn.trigger()
+    def on_tool_add_from_db_clicked(self):
+        """
+        Called when the user wants to add a new tool from Tools Database. It will create the Tools Database object
+        and display the Tools Database tab in the form needed for the Tool adding
+        :return: None
+        """
 
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_poly_mouse_click_release)
-                self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.mr)
-                self.app.plotcanvas.graph_event_disconnect(self.kp)
+        # if the Tools Database is already opened focus on it
+        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.setCurrentWidget(self.app.tools_db_tab)
+                break
+        self.app.on_tools_database(source='iso')
+        self.app.tools_db_tab.ok_to_add = True
+        self.app.tools_db_tab.buttons_frame.hide()
+        self.app.tools_db_tab.add_tool_from_db.show()
+        self.app.tools_db_tab.cancel_tool_from_db.show()
 
-            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                                  self.app.on_mouse_click_release_over_plot)
+    def reset_fields(self):
+        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
 
-            # disconnect flags
-            self.poly_sel_disconnect_flag = False
+    @staticmethod
+    def poly2rings(poly):
+        return [poly.exterior] + [interior for interior in poly.interiors]
 
-            self.app.tool_shapes.clear(update=True)
+    @staticmethod
+    def poly2ext(poly):
+        return [poly.exterior]
 
-            if self.poly_dict:
-                poly_list = deepcopy(list(self.poly_dict.values()))
-                if self.poly_int_cb.get_value() is True:
-                    # isolate the interior polygons with a negative tool
-                    self.isolate(isolated_obj=self.grb_obj, geometry=poly_list, negative_dia=True)
+    @staticmethod
+    def poly2ints(poly):
+        return [interior for interior in poly.interiors]
+
+    def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0,
+                          prog_plot=False):
+        """
+        Isolation_geometry produces an envelope that is going on the left of the geometry
+        (the copper features). To leave the least amount of burrs on the features
+        the tool needs to travel on the right side of the features (this is called conventional milling)
+        the first pass is the one cutting all of the features, so it needs to be reversed
+        the other passes overlap preceding ones and cut the left over copper. It is better for them
+        to cut on the right side of the left over copper i.e on the left side of the features.
+
+        :param offset:          Offset distance to be passed to the obj.isolation_geometry() method
+        :type offset:           float
+        :param invert:          If to invert the direction of geometry (CW to CCW or reverse)
+        :type invert:           int
+        :param geometry:        Shapely Geometry for which t ogenerate envelope
+        :type geometry:
+        :param env_iso_type:    type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete)
+        :type env_iso_type:     int
+        :param follow:          If the kind of isolation is a "follow" one
+        :type follow:           bool
+        :param nr_passes:       Number of passes
+        :type nr_passes:        int
+        :param prog_plot:       Type of plotting: "normal" or "progressive"
+        :type prog_plot:        str
+        :return:                The buffered geometry
+        :rtype:                 MultiPolygon or Polygon
+        """
+
+        if follow:
+            geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, follow=follow, prog_plot=prog_plot)
+            return geom
+        else:
+            try:
+                geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type,
+                                                       passes=nr_passes, prog_plot=prog_plot)
+            except Exception as e:
+                log.debug('ToolIsolation.generate_envelope() --> %s' % str(e))
+                return 'fail'
+
+        if invert:
+            try:
+                pl = []
+                for p in geom:
+                    if p is not None:
+                        if isinstance(p, Polygon):
+                            pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
+                        elif isinstance(p, LinearRing):
+                            pl.append(Polygon(p.coords[::-1]))
+                geom = MultiPolygon(pl)
+            except TypeError:
+                if isinstance(geom, Polygon) and geom is not None:
+                    geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
+                elif isinstance(geom, LinearRing) and geom is not None:
+                    geom = Polygon(geom.coords[::-1])
                 else:
-                    self.isolate(isolated_obj=self.grb_obj, geometry=poly_list)
-                self.poly_dict.clear()
-            else:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
+                    log.debug("ToolIsolation.generate_envelope() Error --> Unexpected Geometry %s" %
+                              type(geom))
+            except Exception as e:
+                log.debug("ToolIsolation.generate_envelope() Error --> %s" % str(e))
+                return 'fail'
+        return geom
 
-    def selection_area_handler(self, start_pos, end_pos, sel_type):
+    @staticmethod
+    def generate_rest_geometry(geometry, tooldia, passes, overlap, invert, env_iso_type=2, negative_dia=None,
+                               forced_rest=False,
+                               prog_plot="normal", prog_plot_handler=None):
         """
-        :param start_pos: mouse position when the selection LMB click was done
-        :param end_pos: mouse position when the left mouse button is released
-        :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
-        :return:
+        Will try to isolate the geometry and return a tuple made of list of paths made through isolation
+        and a list of Shapely Polygons that could not be isolated
+
+        :param geometry:            A list of Shapely Polygons to be isolated
+        :type geometry:             list
+        :param tooldia:             The tool diameter used to do the isolation
+        :type tooldia:              float
+        :param passes:              Number of passes that will made the isolation
+        :type passes:               int
+        :param overlap:             How much to overlap the previous pass; in percentage [0.00, 99.99]%
+        :type overlap:              float
+        :param invert:              If to invert the direction of the resulting isolated geometries
+        :type invert:               bool
+        :param env_iso_type:        can be either 0 = keep exteriors or 1 = keep interiors or 2 = keep all paths
+        :type env_iso_type:         int
+        :param negative_dia:        isolate the geometry with a negative value for the tool diameter
+        :type negative_dia:         bool
+        :param forced_rest:         isolate the polygon even if the interiors can not be isolated
+        :type forced_rest:          bool
+        :param prog_plot:           kind of plotting: "progressive" or "normal"
+        :type prog_plot:            str
+        :param prog_plot_handler:   method used to plot shapes if plot_prog is "proggressive"
+        :type prog_plot_handler:
+        :return:                    Tuple made from list of isolating paths and list of not isolated Polygons
+        :rtype:                     tuple
         """
-        poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
 
-        # delete previous selection shape
-        self.app.delete_selection_shape()
+        isolated_geo = []
+        not_isolated_geo = []
 
-        added_poly_count = 0
-        try:
-            for geo in self.solid_geometry:
-                if geo not in self.poly_dict.values():
-                    if sel_type is True:
-                        if geo.within(poly_selection):
-                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                                shape=geo,
-                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                                face_color=self.app.defaults[
-                                                                               'global_sel_draw_color'] + 'AF',
-                                                                visible=True)
-                            self.poly_dict[shape_id] = geo
-                            added_poly_count += 1
-                    else:
-                        if poly_selection.intersects(geo):
-                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                                shape=geo,
-                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                                face_color=self.app.defaults[
-                                                                               'global_sel_draw_color'] + 'AF',
-                                                                visible=True)
-                            self.poly_dict[shape_id] = geo
-                            added_poly_count += 1
-        except TypeError:
-            if self.solid_geometry not in self.poly_dict.values():
-                if sel_type is True:
-                    if self.solid_geometry.within(poly_selection):
-                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                            shape=self.solid_geometry,
-                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                            face_color=self.app.defaults[
-                                                                           'global_sel_draw_color'] + 'AF',
-                                                            visible=True)
-                        self.poly_dict[shape_id] = self.solid_geometry
-                        added_poly_count += 1
+        work_geo = []
+
+        for idx, geo in enumerate(geometry):
+            good_pass_iso = []
+            start_idx = idx + 1
+
+            for nr_pass in range(passes):
+                iso_offset = tooldia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * tooldia)
+                if negative_dia:
+                    iso_offset = -iso_offset
+
+                buf_chek = iso_offset * 2
+                check_geo = geo.buffer(buf_chek)
+
+                intersect_flag = False
+                # find if current pass for current geo is valid (no intersection with other geos))
+                for geo_search_idx in range(idx):
+                    if check_geo.intersects(geometry[geo_search_idx]):
+                        intersect_flag = True
+                        break
+
+                if intersect_flag is False:
+                    for geo_search_idx in range(start_idx, len(geometry)):
+                        if check_geo.intersects(geometry[geo_search_idx]):
+                            intersect_flag = True
+                            break
+
+                # if we had an intersection do nothing, else add the geo to the good pass isolation's
+                if intersect_flag is False:
+                    temp_geo = geo.buffer(iso_offset)
+                    # this test is done only for the first pass because this is where is relevant
+                    # test if in the first pass, the geo that is isolated has interiors and if it has then test if the
+                    # resulting isolated geometry (buffered) number of subgeo is the same as the exterior + interiors
+                    # if not it means that the geo interiors most likely could not be isolated with this tool so we
+                    # abandon the whole isolation for this geo and add this geo to the not_isolated_geo
+                    if nr_pass == 0 and forced_rest is True:
+                        if geo.interiors:
+                            len_interiors = len(geo.interiors)
+                            if len_interiors > 1:
+                                total_poly_len = 1 + len_interiors  # one exterior + len_interiors of interiors
+
+                                if isinstance(temp_geo, Polygon):
+                                    # calculate the number of subgeos in the buffered geo
+                                    temp_geo_len = len([1] + list(temp_geo.interiors))    # one exterior + interiors
+                                    if total_poly_len != temp_geo_len:
+                                        # some interiors could not be isolated
+                                        break
+                                else:
+                                    try:
+                                        temp_geo_len = len(temp_geo)
+                                        if total_poly_len != temp_geo_len:
+                                            # some interiors could not be isolated
+                                            break
+                                    except TypeError:
+                                        # this means that the buffered geo (temp_geo) is not iterable
+                                        # (one geo element only) therefore failure:
+                                        # we have more interiors but the resulting geo is only one
+                                        break
+
+                    good_pass_iso.append(temp_geo)
+                    if prog_plot == 'progressive':
+                        prog_plot_handler(temp_geo)
+
+            if good_pass_iso:
+                work_geo += good_pass_iso
+            else:
+                not_isolated_geo.append(geo)
+
+        if invert:
+            try:
+                pl = []
+                for p in work_geo:
+                    if p is not None:
+                        if isinstance(p, Polygon):
+                            pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
+                        elif isinstance(p, LinearRing):
+                            pl.append(Polygon(p.coords[::-1]))
+                work_geo = MultiPolygon(pl)
+            except TypeError:
+                if isinstance(work_geo, Polygon) and work_geo is not None:
+                    work_geo = [Polygon(work_geo.exterior.coords[::-1], work_geo.interiors)]
+                elif isinstance(work_geo, LinearRing) and work_geo is not None:
+                    work_geo = [Polygon(work_geo.coords[::-1])]
                 else:
-                    if poly_selection.intersects(self.solid_geometry):
-                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                            shape=self.solid_geometry,
-                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                            face_color=self.app.defaults[
-                                                                           'global_sel_draw_color'] + 'AF',
-                                                            visible=True)
-                        self.poly_dict[shape_id] = self.solid_geometry
-                        added_poly_count += 1
+                    log.debug("ToolIsolation.generate_rest_geometry() Error --> Unexpected Geometry %s" %
+                              type(work_geo))
+            except Exception as e:
+                log.debug("ToolIsolation.generate_rest_geometry() Error --> %s" % str(e))
+                return 'fail', 'fail'
 
-        if added_poly_count > 0:
-            self.app.tool_shapes.redraw()
-            self.app.inform.emit(
-                '%s: %d. %s' % (_("Added polygon"),
-                                int(added_poly_count),
-                                _("Click to add next polygon or right click to start isolation."))
-            )
-        else:
-            self.app.inform.emit(_("No polygon in selection."))
+        if env_iso_type == 0:  # exterior
+            for geo in work_geo:
+                isolated_geo.append(geo.exterior)
+        elif env_iso_type == 1:  # interiors
+            for geo in work_geo:
+                isolated_geo += [interior for interior in geo.interiors]
+        else:  # exterior + interiors
+            for geo in work_geo:
+                isolated_geo += [geo.exterior] + [interior for interior in geo.interiors]
+
+        return isolated_geo, not_isolated_geo
+
+    @staticmethod
+    def get_selected_interior(poly: Polygon, point: tuple) -> [Polygon, None]:
+        try:
+            ints = [Polygon(x) for x in poly.interiors]
+        except AttributeError:
+            return None
 
-    # To be called after clicking on the plot.
-    def on_mouse_release(self, event):
-        if self.app.is_legacy is False:
-            event_pos = event.pos
-            # event_is_dragging = event.is_dragging
-            right_button = 2
-        else:
-            event_pos = (event.xdata, event.ydata)
-            # event_is_dragging = self.app.plotcanvas.is_dragging
-            right_button = 3
+        for poly in ints:
+            if poly.contains(Point(point)):
+                return poly
 
-        event_pos = self.app.plotcanvas.translate_coords(event_pos)
-        if self.app.grid_status():
-            curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
-        else:
-            curr_pos = (event_pos[0], event_pos[1])
+        return None
 
-        x1, y1 = curr_pos[0], curr_pos[1]
+    def find_polygon_ignore_interiors(self, point, geoset=None):
+        """
+        Find an object that object.contains(Point(point)) in
+        poly, which can can be iterable, contain iterable of, or
+        be itself an implementer of .contains(). Will test the Polygon as it is full with no interiors.
 
-        shape_type = self.area_shape_radio.get_value()
+        :param point: See description
+        :param geoset: a polygon or list of polygons where to find if the param point is contained
+        :return: Polygon containing point or None.
+        """
 
-        # do clear area only for left mouse clicks
-        if event.button == 1:
-            if shape_type == "square":
-                if self.first_click is False:
-                    self.first_click = True
-                    self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the paint area."))
+        if geoset is None:
+            geoset = self.solid_geometry
 
-                    self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
-                    if self.app.grid_status():
-                        self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
-                else:
-                    self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
-                    self.app.delete_selection_shape()
+        try:  # Iterable
+            for sub_geo in geoset:
+                p = self.find_polygon_ignore_interiors(point, geoset=sub_geo)
+                if p is not None:
+                    return p
+        except TypeError:  # Non-iterable
+            try:  # Implements .contains()
+                if isinstance(geoset, LinearRing):
+                    geoset = Polygon(geoset)
 
-                    x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
+                poly_ext = Polygon(geoset.exterior)
+                if poly_ext.contains(Point(point)):
+                    return geoset
+            except AttributeError:  # Does not implement .contains()
+                return None
 
-                    pt1 = (x0, y0)
-                    pt2 = (x1, y0)
-                    pt3 = (x1, y1)
-                    pt4 = (x0, y1)
+        return None
 
-                    new_rectangle = Polygon([pt1, pt2, pt3, pt4])
-                    self.sel_rect.append(new_rectangle)
 
-                    # add a temporary shape on canvas
-                    self.draw_tool_selection_shape(old_coords=(x0, y0), coords=(x1, y1))
+class IsoUI:
 
-                    self.first_click = False
-                    return
-            else:
-                self.points.append((x1, y1))
+    toolName = _("Isolation Tool")
 
-                if len(self.points) > 1:
-                    self.poly_drawn = True
-                    self.app.inform.emit(_("Click on next Point or click right mouse button to complete ..."))
+    def __init__(self, layout, app):
+        self.app = app
+        self.decimals = self.app.decimals
+        self.layout = layout
 
-                return ""
-        elif event.button == right_button and self.mouse_is_dragging is False:
+        self.tools_frame = QtWidgets.QFrame()
+        self.tools_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.tools_frame)
+        self.tools_box = QtWidgets.QVBoxLayout()
+        self.tools_box.setContentsMargins(0, 0, 0, 0)
+        self.tools_frame.setLayout(self.tools_box)
 
-            shape_type = self.area_shape_radio.get_value()
+        self.title_box = QtWidgets.QHBoxLayout()
+        self.tools_box.addLayout(self.title_box)
 
-            if shape_type == "square":
-                self.first_click = False
-            else:
-                # if we finish to add a polygon
-                if self.poly_drawn is True:
-                    try:
-                        # try to add the point where we last clicked if it is not already in the self.points
-                        last_pt = (x1, y1)
-                        if last_pt != self.points[-1]:
-                            self.points.append(last_pt)
-                    except IndexError:
-                        pass
+        # ## Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                                QLabel
+                                {
+                                    font-size: 16px;
+                                    font-weight: bold;
+                                }
+                                """)
+        title_label.setToolTip(
+            _("Create a Geometry object with\n"
+              "toolpaths to cut around polygons.")
+        )
 
-                    # we need to add a Polygon and a Polygon can be made only from at least 3 points
-                    if len(self.points) > 2:
-                        self.delete_moving_selection_shape()
-                        pol = Polygon(self.points)
-                        # do not add invalid polygons even if they are drawn by utility geometry
-                        if pol.is_valid:
-                            self.sel_rect.append(pol)
-                            self.draw_selection_shape_polygon(points=self.points)
-                            self.app.inform.emit(
-                                _("Zone added. Click to start adding next zone or right click to finish."))
+        self.title_box.addWidget(title_label)
 
-                    self.points = []
-                    self.poly_drawn = False
-                    return
+        # App Level label
+        self.level = QtWidgets.QLabel("")
+        self.level.setToolTip(
+            _(
+                "BASIC is suitable for a beginner. Many parameters\n"
+                "are hidden from the user in this mode.\n"
+                "ADVANCED mode will make available all parameters.\n\n"
+                "To change the application LEVEL, go to:\n"
+                "Edit -> Preferences -> General and check:\n"
+                "'APP. LEVEL' radio button."
+            )
+        )
+        self.level.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+        self.title_box.addWidget(self.level)
 
-            self.delete_tool_selection_shape()
+        # Grid Layout
+        grid0 = QtWidgets.QGridLayout()
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
+        self.tools_box.addLayout(grid0)
 
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
-                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
-                self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.mr)
-                self.app.plotcanvas.graph_event_disconnect(self.mm)
-                self.app.plotcanvas.graph_event_disconnect(self.kp)
+        self.obj_combo_label = QtWidgets.QLabel('<b>%s</b>:' % _("GERBER"))
+        self.obj_combo_label.setToolTip(
+            _("Gerber object for isolation routing.")
+        )
 
-            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
-                                                                  self.app.on_mouse_click_over_plot)
-            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
-                                                                  self.app.on_mouse_move_over_plot)
-            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                                  self.app.on_mouse_click_release_over_plot)
+        grid0.addWidget(self.obj_combo_label, 0, 0, 1, 2)
 
-            # disconnect flags
-            self.area_sel_disconnect_flag = False
+        # ################################################
+        # ##### The object to be copper cleaned ##########
+        # ################################################
+        self.object_combo = FCComboBox()
+        self.object_combo.setModel(self.app.collection)
+        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        # self.object_combo.setCurrentIndex(1)
+        self.object_combo.is_last = True
 
-            if len(self.sel_rect) == 0:
-                return
+        grid0.addWidget(self.object_combo, 1, 0, 1, 2)
 
-            self.sel_rect = cascaded_union(self.sel_rect)
-            self.isolate(isolated_obj=self.grb_obj, limited_area=self.sel_rect, plot=True)
-            self.sel_rect = []
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 2, 0, 1, 2)
 
-    # called on mouse move
-    def on_mouse_move(self, event):
-        shape_type = self.area_shape_radio.get_value()
+        # ### Tools ## ##
+        self.tools_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Tools Table'))
+        self.tools_table_label.setToolTip(
+            _("Tools pool from which the algorithm\n"
+              "will pick the ones used for copper clearing.")
+        )
+        grid0.addWidget(self.tools_table_label, 3, 0, 1, 2)
 
-        if self.app.is_legacy is False:
-            event_pos = event.pos
-            event_is_dragging = event.is_dragging
-            # right_button = 2
-        else:
-            event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
-            # right_button = 3
+        self.tools_table = FCTable(drag_drop=True)
+        grid0.addWidget(self.tools_table, 4, 0, 1, 2)
 
-        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+        self.tools_table.setColumnCount(4)
+        # 3rd column is reserved (and hidden) for the tool ID
+        self.tools_table.setHorizontalHeaderLabels(['#', _('Diameter'), _('TT'), ''])
+        self.tools_table.setColumnHidden(3, True)
+        self.tools_table.setSortingEnabled(False)
+        # self.tools_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
 
-        # detect mouse dragging motion
-        if event_is_dragging is True:
-            self.mouse_is_dragging = True
-        else:
-            self.mouse_is_dragging = False
+        self.tools_table.horizontalHeaderItem(0).setToolTip(
+            _("This is the Tool Number.\n"
+              "Isolation routing will start with the tool with the biggest \n"
+              "diameter, continuing until there are no more tools.\n"
+              "Only tools that create Isolation geometry will still be present\n"
+              "in the resulting geometry. This is because with some tools\n"
+              "this function will not be able to create routing geometry.")
+        )
+        self.tools_table.horizontalHeaderItem(1).setToolTip(
+            _("Tool Diameter. It's value (in current FlatCAM units)\n"
+              "is the cut width into the material."))
 
-        # update the cursor position
-        if self.app.grid_status():
-            # Update cursor
-            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+        self.tools_table.horizontalHeaderItem(2).setToolTip(
+            _("The Tool Type (TT) can be:\n"
+              "- Circular with 1 ... 4 teeth -> it is informative only. Being circular,\n"
+              "the cut width in material is exactly the tool diameter.\n"
+              "- Ball -> informative only and make reference to the Ball type endmill.\n"
+              "- V-Shape -> it will disable Z-Cut parameter in the resulting geometry UI form\n"
+              "and enable two additional UI form fields in the resulting geometry: V-Tip Dia and\n"
+              "V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such\n"
+              "as the cut width into material will be equal with the value in the Tool Diameter\n"
+              "column of this table.\n"
+              "Choosing the 'V-Shape' Tool Type automatically will select the Operation Type\n"
+              "in the resulting geometry as Isolation."))
 
-            self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
-                                         symbol='++', edge_color=self.app.cursor_color_3D,
-                                         edge_width=self.app.defaults["global_cursor_width"],
-                                         size=self.app.defaults["global_cursor_size"])
+        grid1 = QtWidgets.QGridLayout()
+        grid1.setColumnStretch(0, 0)
+        grid1.setColumnStretch(1, 1)
+        self.tools_box.addLayout(grid1)
 
-        if self.cursor_pos is None:
-            self.cursor_pos = (0, 0)
+        # Tool order
+        self.order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
+        self.order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                      "'No' --> means that the used order is the one in the tool table\n"
+                                      "'Forward' --> means that the tools will be ordered from small to big\n"
+                                      "'Reverse' --> means that the tools will ordered from big to small\n\n"
+                                      "WARNING: using rest machining will automatically set the order\n"
+                                      "in reverse and disable this control."))
 
-        self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
-        self.app.dy = curr_pos[1] - float(self.cursor_pos[1])
+        self.order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
+                                     {'label': _('Forward'), 'value': 'fwd'},
+                                     {'label': _('Reverse'), 'value': 'rev'}])
 
-        # # update the positions on status bar
-        self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                           "<b>Y</b>: %.4f&nbsp;" % (curr_pos[0], curr_pos[1]))
-        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+        grid1.addWidget(self.order_label, 1, 0)
+        grid1.addWidget(self.order_radio, 1, 1)
 
-        units = self.app.defaults["units"].lower()
-        self.app.plotcanvas.text_hud.text = \
-            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
-                self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid1.addWidget(separator_line, 2, 0, 1, 2)
 
-        # draw the utility geometry
-        if shape_type == "square":
-            if self.first_click:
-                self.app.delete_selection_shape()
-                self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
-                                                     coords=(curr_pos[0], curr_pos[1]))
-        else:
-            self.delete_moving_selection_shape()
-            self.draw_moving_selection_shape_poly(points=self.points, data=(curr_pos[0], curr_pos[1]))
+        # #############################################################
+        # ############### Tool selection ##############################
+        # #############################################################
 
-    def on_key_press(self, event):
-        # modifiers = QtWidgets.QApplication.keyboardModifiers()
-        # matplotlib_key_flag = False
+        self.grid3 = QtWidgets.QGridLayout()
+        self.grid3.setColumnStretch(0, 0)
+        self.grid3.setColumnStretch(1, 1)
+        self.tools_box.addLayout(self.grid3)
 
-        # events out of the self.app.collection view (it's about Project Tab) are of type int
-        if type(event) is int:
-            key = event
-        # events from the GUI are of type QKeyEvent
-        elif type(event) == QtGui.QKeyEvent:
-            key = event.key()
-        elif isinstance(event, mpl_key_event):  # MatPlotLib key events are trickier to interpret than the rest
-            # matplotlib_key_flag = True
+        self.tool_sel_label = QtWidgets.QLabel('<b>%s</b>' % _("New Tool"))
+        self.grid3.addWidget(self.tool_sel_label, 1, 0, 1, 2)
 
-            key = event.key
-            key = QtGui.QKeySequence(key)
+        # Tool Type Radio Button
+        self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
+        self.tool_type_label.setToolTip(
+            _("Default tool type:\n"
+              "- 'V-shape'\n"
+              "- Circular")
+        )
 
-            # check for modifiers
-            key_string = key.toString().lower()
-            if '+' in key_string:
-                mod, __, key_text = key_string.rpartition('+')
-                if mod.lower() == 'ctrl':
-                    # modifiers = QtCore.Qt.ControlModifier
-                    pass
-                elif mod.lower() == 'alt':
-                    # modifiers = QtCore.Qt.AltModifier
-                    pass
-                elif mod.lower() == 'shift':
-                    # modifiers = QtCore.Qt.ShiftModifier
-                    pass
-                else:
-                    # modifiers = QtCore.Qt.NoModifier
-                    pass
-                key = QtGui.QKeySequence(key_text)
+        self.tool_type_radio = RadioSet([{'label': _('V-shape'), 'value': 'V'},
+                                         {'label': _('Circular'), 'value': 'C1'}])
+        self.tool_type_radio.setToolTip(
+            _("Default tool type:\n"
+              "- 'V-shape'\n"
+              "- Circular")
+        )
+        self.tool_type_radio.setObjectName("i_tool_type")
 
-        # events from Vispy are of type KeyEvent
-        else:
-            key = event.key
+        self.grid3.addWidget(self.tool_type_label, 2, 0)
+        self.grid3.addWidget(self.tool_type_radio, 2, 1)
 
-        if key == QtCore.Qt.Key_Escape or key == 'Escape':
+        # Tip Dia
+        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
+        self.tipdialabel.setToolTip(
+            _("The tip diameter for V-Shape Tool"))
+        self.tipdia_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.tipdia_entry.set_precision(self.decimals)
+        self.tipdia_entry.set_range(0.0000, 9999.9999)
+        self.tipdia_entry.setSingleStep(0.1)
+        self.tipdia_entry.setObjectName("i_vtipdia")
 
-            if self.area_sel_disconnect_flag is True:
-                if self.app.is_legacy is False:
-                    self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
-                    self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
-                    self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
-                else:
-                    self.app.plotcanvas.graph_event_disconnect(self.mr)
-                    self.app.plotcanvas.graph_event_disconnect(self.mm)
-                    self.app.plotcanvas.graph_event_disconnect(self.kp)
+        self.grid3.addWidget(self.tipdialabel, 3, 0)
+        self.grid3.addWidget(self.tipdia_entry, 3, 1)
 
-                self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
-                                                                      self.app.on_mouse_click_over_plot)
-                self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
-                                                                      self.app.on_mouse_move_over_plot)
-                self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                                      self.app.on_mouse_click_release_over_plot)
+        # Tip Angle
+        self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
+        self.tipanglelabel.setToolTip(
+            _("The tip angle for V-Shape Tool.\n"
+              "In degree."))
+        self.tipangle_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.tipangle_entry.set_precision(self.decimals)
+        self.tipangle_entry.set_range(0.0000, 180.0000)
+        self.tipangle_entry.setSingleStep(5)
+        self.tipangle_entry.setObjectName("i_vtipangle")
 
-            if self.poly_sel_disconnect_flag is False:
-                # restore the Grid snapping if it was active before
-                if self.grid_status_memory is True:
-                    self.app.ui.grid_snap_btn.trigger()
+        self.grid3.addWidget(self.tipanglelabel, 4, 0)
+        self.grid3.addWidget(self.tipangle_entry, 4, 1)
 
-                if self.app.is_legacy is False:
-                    self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_poly_mouse_click_release)
-                    self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
-                else:
-                    self.app.plotcanvas.graph_event_disconnect(self.mr)
-                    self.app.plotcanvas.graph_event_disconnect(self.kp)
+        # Cut Z entry
+        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
+        cutzlabel.setToolTip(
+            _("Depth of cut into material. Negative value.\n"
+              "In FlatCAM units.")
+        )
+        self.cutz_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.cutz_entry.set_precision(self.decimals)
+        self.cutz_entry.set_range(-99999.9999, 0.0000)
+        self.cutz_entry.setObjectName("i_vcutz")
 
-                self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                                      self.app.on_mouse_click_release_over_plot)
+        self.grid3.addWidget(cutzlabel, 5, 0)
+        self.grid3.addWidget(self.cutz_entry, 5, 1)
 
-            self.points = []
-            self.poly_drawn = False
-            self.delete_moving_selection_shape()
-            self.delete_tool_selection_shape()
+        # ### Tool Diameter ####
+        self.addtool_entry_lbl = QtWidgets.QLabel('%s:' % _('Tool Dia'))
+        self.addtool_entry_lbl.setToolTip(
+            _("Diameter for the new tool to add in the Tool Table.\n"
+              "If the tool is V-shape type then this value is automatically\n"
+              "calculated from the other parameters.")
+        )
+        self.addtool_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.addtool_entry.set_precision(self.decimals)
+        self.addtool_entry.set_range(0.000, 9999.9999)
+        self.addtool_entry.setObjectName("i_new_tooldia")
 
-    def on_iso_tool_add_from_db_executed(self, tool):
-        """
-        Here add the tool from DB  in the selected geometry object
-        :return:
-        """
-        tool_from_db = deepcopy(tool)
+        self.grid3.addWidget(self.addtool_entry_lbl, 6, 0)
+        self.grid3.addWidget(self.addtool_entry, 6, 1)
 
-        res = self.on_tool_from_db_inserted(tool=tool_from_db)
+        bhlay = QtWidgets.QHBoxLayout()
 
-        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.addtool_btn = QtWidgets.QPushButton(_('Add'))
+        self.addtool_btn.setToolTip(
+            _("Add a new tool to the Tool Table\n"
+              "with the diameter specified above.")
+        )
 
-        if res == 'fail':
-            return
-        self.app.inform.emit('[success] %s' % _("Tool from DB added in Tool Table."))
+        self.addtool_from_db_btn = QtWidgets.QPushButton(_('Add from DB'))
+        self.addtool_from_db_btn.setToolTip(
+            _("Add a new tool to the Tool Table\n"
+              "from the Tool Database.\n"
+              "Tool database administration in Menu: Options -> Tools Database")
+        )
 
-        # select last tool added
-        toolid = res
-        for row in range(self.tools_table.rowCount()):
-            if int(self.tools_table.item(row, 3).text()) == toolid:
-                self.tools_table.selectRow(row)
-        self.on_row_selection_change()
+        bhlay.addWidget(self.addtool_btn)
+        bhlay.addWidget(self.addtool_from_db_btn)
 
-    def on_tool_from_db_inserted(self, tool):
-        """
-        Called from the Tools DB object through a App method when adding a tool from Tools Database
-        :param tool: a dict with the tool data
-        :return: None
-        """
+        self.grid3.addLayout(bhlay, 7, 0, 1, 2)
 
-        self.ui_disconnect()
-        self.units = self.app.defaults['units'].upper()
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.grid3.addWidget(separator_line, 8, 0, 1, 2)
 
-        tooldia = float(tool['tooldia'])
+        self.deltool_btn = QtWidgets.QPushButton(_('Delete'))
+        self.deltool_btn.setToolTip(
+            _("Delete a selection of tools in the Tool Table\n"
+              "by first selecting a row(s) in the Tool Table.")
+        )
+        self.grid3.addWidget(self.deltool_btn, 9, 0, 1, 2)
 
-        # construct a list of all 'tooluid' in the self.tools
-        tool_uid_list = []
-        for tooluid_key in self.iso_tools:
-            tool_uid_item = int(tooluid_key)
-            tool_uid_list.append(tool_uid_item)
+        # self.grid3.addWidget(QtWidgets.QLabel(''), 10, 0, 1, 2)
 
-        # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
-        if not tool_uid_list:
-            max_uid = 0
-        else:
-            max_uid = max(tool_uid_list)
-        tooluid = max_uid + 1
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.grid3.addWidget(separator_line, 11, 0, 1, 2)
 
-        tooldia = float('%.*f' % (self.decimals, tooldia))
+        self.tool_data_label = QtWidgets.QLabel(
+            "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), int(1)))
+        self.tool_data_label.setToolTip(
+            _(
+                "The data used for creating GCode.\n"
+                "Each tool store it's own set of such data."
+            )
+        )
+        self.grid3.addWidget(self.tool_data_label, 12, 0, 1, 2)
 
-        tool_dias = []
-        for k, v in self.iso_tools.items():
-            for tool_v in v.keys():
-                if tool_v == 'tooldia':
-                    tool_dias.append(float('%.*f' % (self.decimals, (v[tool_v]))))
+        # Passes
+        passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
+        passlabel.setToolTip(
+            _("Width of the isolation gap in\n"
+              "number (integer) of tool widths.")
+        )
+        self.passes_entry = FCSpinner(callback=self.confirmation_message_int)
+        self.passes_entry.set_range(1, 999)
+        self.passes_entry.setObjectName("i_passes")
 
-        if float('%.*f' % (self.decimals, tooldia)) in tool_dias:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. Tool already in Tool Table."))
-            self.ui_connect()
-            return 'fail'
+        self.grid3.addWidget(passlabel, 13, 0)
+        self.grid3.addWidget(self.passes_entry, 13, 1)
 
-        self.iso_tools.update({
-            tooluid: {
-                'tooldia': float('%.*f' % (self.decimals, tooldia)),
-                'offset': tool['offset'],
-                'offset_value': tool['offset_value'],
-                'type': tool['type'],
-                'tool_type': tool['tool_type'],
-                'data': deepcopy(tool['data']),
-                'solid_geometry': []
-            }
-        })
+        # Overlap Entry
+        overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
+        overlabel.setToolTip(
+            _("How much (percentage) of the tool width to overlap each tool pass.")
+        )
+        self.iso_overlap_entry = FCDoubleSpinner(suffix='%', callback=self.confirmation_message)
+        self.iso_overlap_entry.set_precision(self.decimals)
+        self.iso_overlap_entry.setWrapping(True)
+        self.iso_overlap_entry.set_range(0.0000, 99.9999)
+        self.iso_overlap_entry.setSingleStep(0.1)
+        self.iso_overlap_entry.setObjectName("i_overlap")
 
-        self.iso_tools[tooluid]['data']['name'] = '_iso'
+        self.grid3.addWidget(overlabel, 14, 0)
+        self.grid3.addWidget(self.iso_overlap_entry, 14, 1)
 
-        self.app.inform.emit('[success] %s' % _("New tool added to Tool Table."))
+        # 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.ui_connect()
-        self.build_ui()
+        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("i_milling_type")
 
-        # if self.tools_table.rowCount() != 0:
-        #     self.param_frame.setDisabled(False)
+        self.grid3.addWidget(self.milling_type_label, 15, 0)
+        self.grid3.addWidget(self.milling_type_radio, 15, 1)
 
-    def on_tool_add_from_db_clicked(self):
-        """
-        Called when the user wants to add a new tool from Tools Database. It will create the Tools Database object
-        and display the Tools Database tab in the form needed for the Tool adding
-        :return: None
-        """
+        # Follow
+        self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
+        self.follow_label.setToolTip(
+            _("Generate a 'Follow' geometry.\n"
+              "This means that it will cut through\n"
+              "the middle of the trace.")
+        )
 
-        # if the Tools Database is already opened focus on it
-        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.setCurrentWidget(self.app.tools_db_tab)
-                break
-        self.app.on_tools_database(source='iso')
-        self.app.tools_db_tab.ok_to_add = True
-        self.app.tools_db_tab.buttons_frame.hide()
-        self.app.tools_db_tab.add_tool_from_db.show()
-        self.app.tools_db_tab.cancel_tool_from_db.show()
+        self.follow_cb = FCCheckBox()
+        self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
+                                    "This means that it will cut through\n"
+                                    "the middle of the trace."))
+        self.follow_cb.setObjectName("i_follow")
 
-    def reset_fields(self):
-        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.grid3.addWidget(self.follow_label, 16, 0)
+        self.grid3.addWidget(self.follow_cb, 16, 1)
 
-    def reset_usage(self):
-        self.obj_name = ""
-        self.grb_obj = None
+        # Isolation Type
+        self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
+        self.iso_type_label.setToolTip(
+            _("Choose how the isolation will be executed:\n"
+              "- 'Full' -> complete isolation of polygons\n"
+              "- 'Ext' -> will isolate only on the outside\n"
+              "- 'Int' -> will isolate only on the inside\n"
+              "'Exterior' isolation is almost always possible\n"
+              "(with the right tool) but 'Interior'\n"
+              "isolation can be done only when there is an opening\n"
+              "inside of the polygon (e.g polygon is a 'doughnut' shape).")
+        )
+        self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
+                                        {'label': _('Ext'), 'value': 'ext'},
+                                        {'label': _('Int'), 'value': 'int'}])
+        self.iso_type_radio.setObjectName("i_iso_type")
 
-        self.first_click = False
-        self.cursor_pos = None
-        self.mouse_is_dragging = False
+        self.grid3.addWidget(self.iso_type_label, 17, 0)
+        self.grid3.addWidget(self.iso_type_radio, 17, 1)
 
-        prog_plot = True if self.app.defaults["tools_iso_plotting"] == 'progressive' else False
-        if prog_plot:
-            self.temp_shapes.clear(update=True)
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.grid3.addWidget(separator_line, 18, 0, 1, 2)
 
-        self.sel_rect = []
+        self.apply_param_to_all = FCButton(_("Apply parameters to all tools"))
+        self.apply_param_to_all.setToolTip(
+            _("The parameters in the current form will be applied\n"
+              "on all the tools from the Tool Table.")
+        )
+        self.grid3.addWidget(self.apply_param_to_all, 22, 0, 1, 2)
 
-    @staticmethod
-    def poly2rings(poly):
-        return [poly.exterior] + [interior for interior in poly.interiors]
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.grid3.addWidget(separator_line, 23, 0, 1, 2)
 
-    @staticmethod
-    def poly2ext(poly):
-        return [poly.exterior]
+        # General Parameters
+        self.gen_param_label = QtWidgets.QLabel('<b>%s</b>' % _("Common Parameters"))
+        self.gen_param_label.setToolTip(
+            _("Parameters that are common for all tools.")
+        )
+        self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
 
-    @staticmethod
-    def poly2ints(poly):
-        return [interior for interior in poly.interiors]
+        # Rest Machining
+        self.rest_cb = FCCheckBox('%s' % _("Rest"))
+        self.rest_cb.setObjectName("i_rest")
+        self.rest_cb.setToolTip(
+            _("If checked, use 'rest machining'.\n"
+              "Basically it will isolate outside PCB features,\n"
+              "using the biggest tool and continue with the next tools,\n"
+              "from bigger to smaller, to isolate the copper features that\n"
+              "could not be cleared by previous tool, until there is\n"
+              "no more copper features to isolate or there are no more tools.\n"
+              "If not checked, use the standard algorithm.")
+        )
 
-    def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0,
-                          prog_plot=False):
-        """
-        Isolation_geometry produces an envelope that is going on the left of the geometry
-        (the copper features). To leave the least amount of burrs on the features
-        the tool needs to travel on the right side of the features (this is called conventional milling)
-        the first pass is the one cutting all of the features, so it needs to be reversed
-        the other passes overlap preceding ones and cut the left over copper. It is better for them
-        to cut on the right side of the left over copper i.e on the left side of the features.
+        self.grid3.addWidget(self.rest_cb, 25, 0)
 
-        :param offset:          Offset distance to be passed to the obj.isolation_geometry() method
-        :type offset:           float
-        :param invert:          If to invert the direction of geometry (CW to CCW or reverse)
-        :type invert:           int
-        :param geometry:        Shapely Geometry for which t ogenerate envelope
-        :type geometry:
-        :param env_iso_type:    type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete)
-        :type env_iso_type:     int
-        :param follow:          If the kind of isolation is a "follow" one
-        :type follow:           bool
-        :param nr_passes:       Number of passes
-        :type nr_passes:        int
-        :param prog_plot:       Type of plotting: "normal" or "progressive"
-        :type prog_plot:        str
-        :return:                The buffered geometry
-        :rtype:                 MultiPolygon or Polygon
-        """
+        # Force isolation even if the interiors are not isolated
+        self.forced_rest_iso_cb = FCCheckBox(_("Forced Rest"))
+        self.forced_rest_iso_cb.setToolTip(
+            _("When checked the isolation will be done with the current tool even if\n"
+              "interiors of a polygon (holes in the polygon) could not be isolated.\n"
+              "Works when 'rest machining' is used.")
+        )
 
-        if follow:
-            geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, follow=follow, prog_plot=prog_plot)
-            return geom
-        else:
-            try:
-                geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type,
-                                                       passes=nr_passes, prog_plot=prog_plot)
-            except Exception as e:
-                log.debug('ToolIsolation.generate_envelope() --> %s' % str(e))
-                return 'fail'
+        self.grid3.addWidget(self.forced_rest_iso_cb, 25, 1)
 
-        if invert:
-            try:
-                pl = []
-                for p in geom:
-                    if p is not None:
-                        if isinstance(p, Polygon):
-                            pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
-                        elif isinstance(p, LinearRing):
-                            pl.append(Polygon(p.coords[::-1]))
-                geom = MultiPolygon(pl)
-            except TypeError:
-                if isinstance(geom, Polygon) and geom is not None:
-                    geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
-                elif isinstance(geom, LinearRing) and geom is not None:
-                    geom = Polygon(geom.coords[::-1])
-                else:
-                    log.debug("ToolIsolation.generate_envelope() Error --> Unexpected Geometry %s" %
-                              type(geom))
-            except Exception as e:
-                log.debug("ToolIsolation.generate_envelope() Error --> %s" % str(e))
-                return 'fail'
-        return geom
+        # Combine All Passes
+        self.combine_passes_cb = FCCheckBox(label=_('Combine'))
+        self.combine_passes_cb.setToolTip(
+            _("Combine all passes into one object")
+        )
+        self.combine_passes_cb.setObjectName("i_combine")
 
-    @staticmethod
-    def generate_rest_geometry(geometry, tooldia, passes, overlap, invert, env_iso_type=2, negative_dia=None,
-                               forced_rest=False,
-                               prog_plot="normal", prog_plot_handler=None):
-        """
-        Will try to isolate the geometry and return a tuple made of list of paths made through isolation
-        and a list of Shapely Polygons that could not be isolated
+        self.grid3.addWidget(self.combine_passes_cb, 26, 0, 1, 2)
 
-        :param geometry:            A list of Shapely Polygons to be isolated
-        :type geometry:             list
-        :param tooldia:             The tool diameter used to do the isolation
-        :type tooldia:              float
-        :param passes:              Number of passes that will made the isolation
-        :type passes:               int
-        :param overlap:             How much to overlap the previous pass; in percentage [0.00, 99.99]%
-        :type overlap:              float
-        :param invert:              If to invert the direction of the resulting isolated geometries
-        :type invert:               bool
-        :param env_iso_type:        can be either 0 = keep exteriors or 1 = keep interiors or 2 = keep all paths
-        :type env_iso_type:         int
-        :param negative_dia:        isolate the geometry with a negative value for the tool diameter
-        :type negative_dia:         bool
-        :param forced_rest:         isolate the polygon even if the interiors can not be isolated
-        :type forced_rest:          bool
-        :param prog_plot:           kind of plotting: "progressive" or "normal"
-        :type prog_plot:            str
-        :param prog_plot_handler:   method used to plot shapes if plot_prog is "proggressive"
-        :type prog_plot_handler:
-        :return:                    Tuple made from list of isolating paths and list of not isolated Polygons
-        :rtype:                     tuple
-        """
+        # Exception Areas
+        self.except_cb = FCCheckBox(label=_('Except'))
+        self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
+                                    "by checking this, the area of the object below\n"
+                                    "will be subtracted from the isolation geometry."))
+        self.except_cb.setObjectName("i_except")
+        self.grid3.addWidget(self.except_cb, 27, 0)
 
-        isolated_geo = []
-        not_isolated_geo = []
+        # Type of object to be excepted
+        self.type_excobj_radio = RadioSet([{'label': _("Geometry"), 'value': 'geometry'},
+                                           {'label': _("Gerber"), 'value': 'gerber'}])
+        self.type_excobj_radio.setToolTip(
+            _("Specify the type of object to be excepted from isolation.\n"
+              "It can be of type: Gerber or Geometry.\n"
+              "What is selected here will dictate the kind\n"
+              "of objects that will populate the 'Object' combobox.")
+        )
 
-        work_geo = []
+        self.grid3.addWidget(self.type_excobj_radio, 27, 1)
 
-        for idx, geo in enumerate(geometry):
-            good_pass_iso = []
-            start_idx = idx + 1
+        # The object to be excepted
+        self.exc_obj_combo = FCComboBox()
+        self.exc_obj_combo.setToolTip(_("Object whose area will be removed from isolation geometry."))
 
-            for nr_pass in range(passes):
-                iso_offset = tooldia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * tooldia)
-                if negative_dia:
-                    iso_offset = -iso_offset
+        # set the model for the Area Exception comboboxes
+        self.exc_obj_combo.setModel(self.app.collection)
+        self.exc_obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.exc_obj_combo.is_last = True
+        self.exc_obj_combo.obj_type = self.type_excobj_radio.get_value()
 
-                buf_chek = iso_offset * 2
-                check_geo = geo.buffer(buf_chek)
+        self.grid3.addWidget(self.exc_obj_combo, 29, 0, 1, 2)
 
-                intersect_flag = False
-                # find if current pass for current geo is valid (no intersection with other geos))
-                for geo_search_idx in range(idx):
-                    if check_geo.intersects(geometry[geo_search_idx]):
-                        intersect_flag = True
-                        break
+        self.e_ois = OptionalInputSection(self.except_cb,
+                                          [
+                                              self.type_excobj_radio,
+                                              self.exc_obj_combo
+                                          ])
 
-                if intersect_flag is False:
-                    for geo_search_idx in range(start_idx, len(geometry)):
-                        if check_geo.intersects(geometry[geo_search_idx]):
-                            intersect_flag = True
-                            break
+        # Isolation Scope
+        self.select_label = QtWidgets.QLabel('%s:' % _("Selection"))
+        self.select_label.setToolTip(
+            _("Isolation scope. Choose what to isolate:\n"
+              "- 'All' -> Isolate all the polygons in the object\n"
+              "- 'Area Selection' -> Isolate polygons within a selection area.\n"
+              "- 'Polygon Selection' -> Isolate a selection of polygons.\n"
+              "- 'Reference Object' - will process the area specified by another object.")
+        )
+        self.select_combo = FCComboBox()
+        self.select_combo.addItems(
+            [_("All"), _("Area Selection"), _("Polygon Selection"), _("Reference Object")]
+        )
+        self.select_combo.setObjectName("i_selection")
 
-                # if we had an intersection do nothing, else add the geo to the good pass isolation's
-                if intersect_flag is False:
-                    temp_geo = geo.buffer(iso_offset)
-                    # this test is done only for the first pass because this is where is relevant
-                    # test if in the first pass, the geo that is isolated has interiors and if it has then test if the
-                    # resulting isolated geometry (buffered) number of subgeo is the same as the exterior + interiors
-                    # if not it means that the geo interiors most likely could not be isolated with this tool so we
-                    # abandon the whole isolation for this geo and add this geo to the not_isolated_geo
-                    if nr_pass == 0 and forced_rest is True:
-                        if geo.interiors:
-                            len_interiors = len(geo.interiors)
-                            if len_interiors > 1:
-                                total_poly_len = 1 + len_interiors  # one exterior + len_interiors of interiors
+        self.grid3.addWidget(self.select_label, 30, 0)
+        self.grid3.addWidget(self.select_combo, 30, 1)
 
-                                if isinstance(temp_geo, Polygon):
-                                    # calculate the number of subgeos in the buffered geo
-                                    temp_geo_len = len([1] + list(temp_geo.interiors))    # one exterior + interiors
-                                    if total_poly_len != temp_geo_len:
-                                        # some interiors could not be isolated
-                                        break
-                                else:
-                                    try:
-                                        temp_geo_len = len(temp_geo)
-                                        if total_poly_len != temp_geo_len:
-                                            # some interiors could not be isolated
-                                            break
-                                    except TypeError:
-                                        # this means that the buffered geo (temp_geo) is not iterable
-                                        # (one geo element only) therefore failure:
-                                        # we have more interiors but the resulting geo is only one
-                                        break
+        self.reference_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
+        self.reference_combo_type_label.setToolTip(
+            _("The type of FlatCAM object to be used as non copper clearing reference.\n"
+              "It can be Gerber, Excellon or Geometry.")
+        )
+        self.reference_combo_type = FCComboBox()
+        self.reference_combo_type.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
 
-                    good_pass_iso.append(temp_geo)
-                    if prog_plot == 'progressive':
-                        prog_plot_handler(temp_geo)
+        self.grid3.addWidget(self.reference_combo_type_label, 31, 0)
+        self.grid3.addWidget(self.reference_combo_type, 31, 1)
 
-            if good_pass_iso:
-                work_geo += good_pass_iso
-            else:
-                not_isolated_geo.append(geo)
+        self.reference_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
+        self.reference_combo_label.setToolTip(
+            _("The FlatCAM object to be used as non copper clearing reference.")
+        )
+        self.reference_combo = FCComboBox()
+        self.reference_combo.setModel(self.app.collection)
+        self.reference_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.reference_combo.is_last = True
 
-        if invert:
-            try:
-                pl = []
-                for p in work_geo:
-                    if p is not None:
-                        if isinstance(p, Polygon):
-                            pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
-                        elif isinstance(p, LinearRing):
-                            pl.append(Polygon(p.coords[::-1]))
-                work_geo = MultiPolygon(pl)
-            except TypeError:
-                if isinstance(work_geo, Polygon) and work_geo is not None:
-                    work_geo = [Polygon(work_geo.exterior.coords[::-1], work_geo.interiors)]
-                elif isinstance(work_geo, LinearRing) and work_geo is not None:
-                    work_geo = [Polygon(work_geo.coords[::-1])]
-                else:
-                    log.debug("ToolIsolation.generate_rest_geometry() Error --> Unexpected Geometry %s" %
-                              type(work_geo))
-            except Exception as e:
-                log.debug("ToolIsolation.generate_rest_geometry() Error --> %s" % str(e))
-                return 'fail', 'fail'
+        self.grid3.addWidget(self.reference_combo_label, 32, 0)
+        self.grid3.addWidget(self.reference_combo, 32, 1)
 
-        if env_iso_type == 0:  # exterior
-            for geo in work_geo:
-                isolated_geo.append(geo.exterior)
-        elif env_iso_type == 1:  # interiors
-            for geo in work_geo:
-                isolated_geo += [interior for interior in geo.interiors]
-        else:  # exterior + interiors
-            for geo in work_geo:
-                isolated_geo += [geo.exterior] + [interior for interior in geo.interiors]
+        self.reference_combo.hide()
+        self.reference_combo_label.hide()
+        self.reference_combo_type.hide()
+        self.reference_combo_type_label.hide()
 
-        return isolated_geo, not_isolated_geo
+        # Polygon interiors selection
+        self.poly_int_cb = FCCheckBox(_("Interiors"))
+        self.poly_int_cb.setToolTip(
+            _("When checked the user can select interiors of a polygon.\n"
+              "(holes in the polygon).")
+        )
 
-    @staticmethod
-    def get_selected_interior(poly: Polygon, point: tuple) -> [Polygon, None]:
-        try:
-            ints = [Polygon(x) for x in poly.interiors]
-        except AttributeError:
-            return None
+        self.grid3.addWidget(self.poly_int_cb, 33, 0)
 
-        for poly in ints:
-            if poly.contains(Point(point)):
-                return poly
+        self.poly_int_cb.hide()
 
-        return None
+        # Area Selection shape
+        self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
+        self.area_shape_label.setToolTip(
+            _("The kind of selection shape used for area selection.")
+        )
 
-    def find_polygon_ignore_interiors(self, point, geoset=None):
-        """
-        Find an object that object.contains(Point(point)) in
-        poly, which can can be iterable, contain iterable of, or
-        be itself an implementer of .contains(). Will test the Polygon as it is full with no interiors.
+        self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
+                                          {'label': _("Polygon"), 'value': 'polygon'}])
 
-        :param point: See description
-        :param geoset: a polygon or list of polygons where to find if the param point is contained
-        :return: Polygon containing point or None.
-        """
+        self.grid3.addWidget(self.area_shape_label, 35, 0)
+        self.grid3.addWidget(self.area_shape_radio, 35, 1)
 
-        if geoset is None:
-            geoset = self.solid_geometry
+        self.area_shape_label.hide()
+        self.area_shape_radio.hide()
 
-        try:  # Iterable
-            for sub_geo in geoset:
-                p = self.find_polygon_ignore_interiors(point, geoset=sub_geo)
-                if p is not None:
-                    return p
-        except TypeError:  # Non-iterable
-            try:  # Implements .contains()
-                if isinstance(geoset, LinearRing):
-                    geoset = Polygon(geoset)
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.grid3.addWidget(separator_line, 36, 0, 1, 2)
 
-                poly_ext = Polygon(geoset.exterior)
-                if poly_ext.contains(Point(point)):
-                    return geoset
-            except AttributeError:  # Does not implement .contains()
-                return None
+        self.generate_iso_button = QtWidgets.QPushButton("%s" % _("Generate Isolation Geometry"))
+        self.generate_iso_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        self.generate_iso_button.setToolTip(
+            _("Create a Geometry object with toolpaths to cut \n"
+              "isolation outside, inside or on both sides of the\n"
+              "object. For a Gerber object outside means outside\n"
+              "of the Gerber feature and inside means inside of\n"
+              "the Gerber feature, if possible at all. This means\n"
+              "that only if the Gerber feature has openings inside, they\n"
+              "will be isolated. If what is wanted is to cut isolation\n"
+              "inside the actual Gerber feature, use a negative tool\n"
+              "diameter above.")
+        )
+        self.tools_box.addWidget(self.generate_iso_button)
 
-        return None
+        self.create_buffer_button = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
+        self.create_buffer_button.setToolTip(
+            _("This button is shown only when the Gerber file\n"
+              "is loaded without buffering.\n"
+              "Clicking this will create the buffered geometry\n"
+              "required for isolation.")
+        )
+        self.tools_box.addWidget(self.create_buffer_button)
+
+        self.tools_box.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        self.tools_box.addWidget(self.reset_button)
+        # ############################ FINSIHED GUI ###################################
+        # #############################################################################
+
+    def confirmation_message(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
+                                                                                  self.decimals,
+                                                                                  minval,
+                                                                                  self.decimals,
+                                                                                  maxval), False)
+        else:
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+
+    def confirmation_message_int(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                            (_("Edited value is out of range"), minval, maxval), False)
+        else:
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)