Marius Stanciu 5 лет назад
Родитель
Сommit
47dd8b0dfe
62 измененных файлов с 2143 добавлено и 2304 удалено
  1. 191 2
      AppDatabase.py
  2. 1 1
      AppEditors/FlatCAMExcEditor.py
  3. 3 3
      AppEditors/FlatCAMGeoEditor.py
  4. 1 1
      AppEditors/FlatCAMGrbEditor.py
  5. 2 2
      AppEditors/FlatCAMTextEditor.py
  6. 36 12
      AppGUI/GUIElements.py
  7. 144 109
      AppGUI/MainGUI.py
  8. 89 316
      AppGUI/ObjectUI.py
  9. 82 17
      AppGUI/PlotCanvas.py
  10. 71 12
      AppGUI/PlotCanvasLegacy.py
  11. 120 106
      AppGUI/preferences/PreferencesUIManager.py
  12. 3 3
      AppGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py
  13. 10 1
      AppGUI/preferences/excellon/ExcellonGenPrefGroupUI.py
  14. 2 2
      AppGUI/preferences/excellon/ExcellonOptPrefGroupUI.py
  15. 1 1
      AppGUI/preferences/excellon/ExcellonPreferencesUI.py
  16. 0 8
      AppGUI/preferences/general/GeneralAPPSetGroupUI.py
  17. 18 10
      AppGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py
  18. 11 1
      AppGUI/preferences/geometry/GeometryGenPrefGroupUI.py
  19. 3 2
      AppGUI/preferences/geometry/GeometryOptPrefGroupUI.py
  20. 0 79
      AppGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py
  21. 3 2
      AppGUI/preferences/gerber/GerberEditorPrefGroupUI.py
  22. 9 9
      AppGUI/preferences/gerber/GerberGenPrefGroupUI.py
  23. 0 90
      AppGUI/preferences/gerber/GerberOptPrefGroupUI.py
  24. 1 0
      AppGUI/preferences/gerber/GerberPreferencesUI.py
  25. 2 2
      AppGUI/preferences/tools/Tools2CalPrefGroupUI.py
  26. 319 0
      AppGUI/preferences/tools/ToolsISOPrefGroupUI.py
  27. 9 9
      AppGUI/preferences/tools/ToolsNCCPrefGroupUI.py
  28. 8 8
      AppGUI/preferences/tools/ToolsPaintPrefGroupUI.py
  29. 8 2
      AppGUI/preferences/tools/ToolsPreferencesUI.py
  30. 3 3
      AppGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py
  31. 2 2
      AppGUI/preferences/tools/ToolsTransformPrefGroupUI.py
  32. 2 2
      AppObjects/FlatCAMCNCJob.py
  33. 85 15
      AppObjects/FlatCAMExcellon.py
  34. 33 1
      AppObjects/FlatCAMGeometry.py
  35. 18 680
      AppObjects/FlatCAMGerber.py
  36. 13 13
      AppObjects/FlatCAMObj.py
  37. 3 3
      AppObjects/ObjectCollection.py
  38. 9 5
      AppTool.py
  39. 14 6
      AppTools/ToolCutOut.py
  40. 71 16
      AppTools/ToolEtchCompensation.py
  41. 2 2
      AppTools/ToolFilm.py
  42. 341 396
      AppTools/ToolIsolation.py
  43. 7 5
      AppTools/ToolNCC.py
  44. 3 3
      AppTools/ToolPaint.py
  45. 4 4
      AppTools/ToolQRCode.py
  46. 7 7
      AppTools/ToolRulesCheck.py
  47. 12 12
      AppTools/ToolShell.py
  48. 3 2
      AppTools/ToolSolderPaste.py
  49. 130 131
      AppTools/ToolSub.py
  50. 4 0
      AppTranslation.py
  51. 131 162
      App_Main.py
  52. 1 1
      Bookmark.py
  53. 51 0
      CHANGELOG.md
  54. 2 2
      Common.py
  55. BIN
      assets/resources/axis16.png
  56. BIN
      assets/resources/grid32.png
  57. BIN
      assets/resources/grid_lines32.png
  58. BIN
      assets/resources/shell20.png
  59. 17 6
      camlib.py
  60. 26 13
      defaults.py
  61. 1 1
      tclCommands/TclCommandIsolate.py
  62. 1 1
      tclCommands/TclCommandPaint.py

+ 191 - 2
AppDatabase.py

@@ -8,6 +8,8 @@ import json
 
 
 from copy import deepcopy
 from copy import deepcopy
 from datetime import datetime
 from datetime import datetime
+import math
+
 import gettext
 import gettext
 import AppTranslation as fcTranslate
 import AppTranslation as fcTranslate
 import builtins
 import builtins
@@ -655,7 +657,7 @@ class ToolsDB(QtWidgets.QWidget):
                                                                l_save=str(self.app.get_last_save_folder()),
                                                                l_save=str(self.app.get_last_save_folder()),
                                                                n=_("Tools_Database"),
                                                                n=_("Tools_Database"),
                                                                date=date),
                                                                date=date),
-                                                           filter=filter__)
+                                                           ext_filter=filter__)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -1030,6 +1032,7 @@ class ToolsDB2(QtWidgets.QWidget):
         self.advanced_box.setTitle(_("Advanced Geo Parameters"))
         self.advanced_box.setTitle(_("Advanced Geo Parameters"))
         self.advanced_box.setFixedWidth(250)
         self.advanced_box.setFixedWidth(250)
 
 
+        # NCC TOOL BOX
         self.ncc_box = QtWidgets.QGroupBox()
         self.ncc_box = QtWidgets.QGroupBox()
         self.ncc_box.setStyleSheet("""
         self.ncc_box.setStyleSheet("""
                         QGroupBox
                         QGroupBox
@@ -1042,6 +1045,7 @@ class ToolsDB2(QtWidgets.QWidget):
         self.ncc_box.setTitle(_("NCC Parameters"))
         self.ncc_box.setTitle(_("NCC Parameters"))
         self.ncc_box.setFixedWidth(250)
         self.ncc_box.setFixedWidth(250)
 
 
+        # PAINT TOOL BOX
         self.paint_box = QtWidgets.QGroupBox()
         self.paint_box = QtWidgets.QGroupBox()
         self.paint_box.setStyleSheet("""
         self.paint_box.setStyleSheet("""
                         QGroupBox
                         QGroupBox
@@ -1054,10 +1058,24 @@ class ToolsDB2(QtWidgets.QWidget):
         self.paint_box.setTitle(_("Paint Parameters"))
         self.paint_box.setTitle(_("Paint Parameters"))
         self.paint_box.setFixedWidth(250)
         self.paint_box.setFixedWidth(250)
 
 
+        # ISOLATION TOOL BOX
+        self.iso_box = QtWidgets.QGroupBox()
+        self.iso_box.setStyleSheet("""
+                     QGroupBox
+                     {
+                         font-size: 16px;
+                         font-weight: bold;
+                     }
+                     """)
+        self.iso_vlay = QtWidgets.QVBoxLayout()
+        self.iso_box.setTitle(_("Isolation Parameters"))
+        self.iso_box.setFixedWidth(250)
+
         self.basic_box.setLayout(self.basic_vlay)
         self.basic_box.setLayout(self.basic_vlay)
         self.advanced_box.setLayout(self.advanced_vlay)
         self.advanced_box.setLayout(self.advanced_vlay)
         self.ncc_box.setLayout(self.ncc_vlay)
         self.ncc_box.setLayout(self.ncc_vlay)
         self.paint_box.setLayout(self.paint_vlay)
         self.paint_box.setLayout(self.paint_vlay)
+        self.iso_box.setLayout(self.iso_vlay)
 
 
         geo_vlay = QtWidgets.QVBoxLayout()
         geo_vlay = QtWidgets.QVBoxLayout()
         geo_vlay.addWidget(self.basic_box)
         geo_vlay.addWidget(self.basic_box)
@@ -1067,6 +1085,7 @@ class ToolsDB2(QtWidgets.QWidget):
         tools_vlay = QtWidgets.QVBoxLayout()
         tools_vlay = QtWidgets.QVBoxLayout()
         tools_vlay.addWidget(self.ncc_box)
         tools_vlay.addWidget(self.ncc_box)
         tools_vlay.addWidget(self.paint_box)
         tools_vlay.addWidget(self.paint_box)
+        tools_vlay.addWidget(self.iso_box)
         tools_vlay.addStretch()
         tools_vlay.addStretch()
 
 
         param_hlay.addLayout(geo_vlay)
         param_hlay.addLayout(geo_vlay)
@@ -1621,6 +1640,101 @@ class ToolsDB2(QtWidgets.QWidget):
         self.grid3.addWidget(self.pathconnect_cb, 10, 0)
         self.grid3.addWidget(self.pathconnect_cb, 10, 0)
         self.grid3.addWidget(self.paintcontour_cb, 10, 1)
         self.grid3.addWidget(self.paintcontour_cb, 10, 1)
 
 
+        # ###########################################################################
+        # ############### Paint UI form #############################################
+        # ###########################################################################
+
+        self.grid4 = QtWidgets.QGridLayout()
+        self.iso_vlay.addLayout(self.grid4)
+        self.grid4.setColumnStretch(0, 0)
+        self.grid4.setColumnStretch(1, 1)
+        self.iso_vlay.addStretch()
+
+        # Passes
+        passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
+        passlabel.setToolTip(
+            _("Width of the isolation gap in\n"
+              "number (integer) of tool widths.")
+        )
+        self.passes_entry = FCSpinner()
+        self.passes_entry.set_range(1, 999)
+        self.passes_entry.setObjectName("gdb_i_passes")
+
+        self.grid4.addWidget(passlabel, 0, 0)
+        self.grid4.addWidget(self.passes_entry, 0, 1)
+
+        # 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='%')
+        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("gdb_i_overlap")
+
+        self.grid4.addWidget(overlabel, 2, 0)
+        self.grid4.addWidget(self.iso_overlap_entry, 2, 1)
+
+        # Milling Type Radio Button
+        self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
+        self.milling_type_label.setToolTip(
+            _("Milling type when the selected tool is of type: 'iso_op':\n"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+
+        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
+                                            {'label': _('Conventional'), 'value': 'cv'}])
+        self.milling_type_radio.setToolTip(
+            _("Milling type when the selected tool is of type: 'iso_op':\n"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+        self.milling_type_radio.setObjectName("gdb_i_milling_type")
+
+        self.grid4.addWidget(self.milling_type_label, 4, 0)
+        self.grid4.addWidget(self.milling_type_radio, 4, 1)
+
+        # 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.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("gdb_i_follow")
+
+        self.grid4.addWidget(self.follow_label, 6, 0)
+        self.grid4.addWidget(self.follow_cb, 6, 1)
+
+        # 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("gdb_i_iso_type")
+
+        self.grid4.addWidget(self.iso_type_label, 8, 0)
+        self.grid4.addWidget(self.iso_type_radio, 8, 1)
+
         # ####################################################################
         # ####################################################################
         # ####################################################################
         # ####################################################################
         # GUI for the lower part of the window
         # GUI for the lower part of the window
@@ -1743,6 +1857,13 @@ class ToolsDB2(QtWidgets.QWidget):
             "tools_paintmethod":        self.paintmethod_combo,
             "tools_paintmethod":        self.paintmethod_combo,
             "tools_pathconnect":        self.pathconnect_cb,
             "tools_pathconnect":        self.pathconnect_cb,
             "tools_paintcontour":       self.paintcontour_cb,
             "tools_paintcontour":       self.paintcontour_cb,
+
+            # Isolation
+            "tools_iso_passes":         self.passes_entry,
+            "tools_iso_overlap":        self.iso_overlap_entry,
+            "tools_iso_milling_type":   self.milling_type_radio,
+            "tools_iso_follow":         self.follow_cb,
+            "tools_iso_isotype":        self.iso_type_radio
         }
         }
 
 
         self.name2option = {
         self.name2option = {
@@ -1787,6 +1908,13 @@ class ToolsDB2(QtWidgets.QWidget):
             'gdb_p_method':         "tools_paintmethod",
             'gdb_p_method':         "tools_paintmethod",
             'gdb_p_connect':        "tools_pathconnect",
             'gdb_p_connect':        "tools_pathconnect",
             'gdb_p_contour':        "tools_paintcontour",
             'gdb_p_contour':        "tools_paintcontour",
+
+            # Isolation
+            "gdb_i_passes":         "tools_iso_passes",
+            "gdb_i_overlap":        "tools_iso_overlap",
+            "gdb_i_milling_type":   "tools_iso_milling_type",
+            "gdb_i_follow":         "tools_iso_follow",
+            "gdb_i_iso_type":       "tools_iso_isotype"
         }
         }
 
 
         self.current_toolid = None
         self.current_toolid = None
@@ -1944,6 +2072,7 @@ class ToolsDB2(QtWidgets.QWidget):
                 self.advanced_box.setEnabled(True)
                 self.advanced_box.setEnabled(True)
                 self.ncc_box.setEnabled(True)
                 self.ncc_box.setEnabled(True)
                 self.paint_box.setEnabled(True)
                 self.paint_box.setEnabled(True)
+                self.iso_box.setEnabled(True)
 
 
                 self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0))
                 self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0))
                 # self.tree_widget.setFocus()
                 # self.tree_widget.setFocus()
@@ -1954,6 +2083,7 @@ class ToolsDB2(QtWidgets.QWidget):
                 self.advanced_box.setEnabled(False)
                 self.advanced_box.setEnabled(False)
                 self.ncc_box.setEnabled(False)
                 self.ncc_box.setEnabled(False)
                 self.paint_box.setEnabled(False)
                 self.paint_box.setEnabled(False)
+                self.iso_box.setEnabled(False)
         else:
         else:
             self.storage_to_form(self.db_tool_dict[str(self.current_toolid)])
             self.storage_to_form(self.db_tool_dict[str(self.current_toolid)])
 
 
@@ -2006,6 +2136,13 @@ class ToolsDB2(QtWidgets.QWidget):
             "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
             "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
             "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
             "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
             "tools_paintcontour":       self.app.defaults["tools_paintcontour"],
             "tools_paintcontour":       self.app.defaults["tools_paintcontour"],
+
+            # Isolation
+            "tools_iso_passes":         int(self.app.defaults["tools_iso_passes"]),
+            "tools_iso_overlap":        float(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"],
         })
         })
 
 
         dict_elem = {}
         dict_elem = {}
@@ -2117,7 +2254,7 @@ class ToolsDB2(QtWidgets.QWidget):
                                                                 l_save=str(self.app.get_last_save_folder()),
                                                                 l_save=str(self.app.get_last_save_folder()),
                                                                 n=_("Tools_Database"),
                                                                 n=_("Tools_Database"),
                                                                 date=date),
                                                                 date=date),
-                                                           filter=filter__)
+                                                           ext_filter=filter__)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -2218,6 +2355,18 @@ class ToolsDB2(QtWidgets.QWidget):
         self.app.tools_db_changed_flag = False
         self.app.tools_db_changed_flag = False
         self.on_save_tools_db()
         self.on_save_tools_db()
 
 
+    def on_calculate_tooldia(self):
+        if self.shape_combo.get_value() == 'V':
+            tip_dia = float(self.vdia_entry.get_value())
+            half_tip_angle = float(self.vangle_entry.get_value()) / 2.0
+            cut_z = float(self.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(half_tip_angle)))
+
+            self.dia_entry.set_value(tool_dia)
+
     def ui_connect(self):
     def ui_connect(self):
         # make sure that we don't make multiple connections to the widgets
         # make sure that we don't make multiple connections to the widgets
         self.ui_disconnect()
         self.ui_disconnect()
@@ -2247,12 +2396,40 @@ class ToolsDB2(QtWidgets.QWidget):
             if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
             if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
                 wdg.valueChanged.connect(self.update_storage)
                 wdg.valueChanged.connect(self.update_storage)
 
 
+        # connect the calculate tooldia method to the controls
+        # if the tool shape is 'V' the tool dia will be calculated to obey Cut Z parameter
+        self.shape_combo.currentIndexChanged.connect(self.on_calculate_tooldia)
+        self.cutz_entry.valueChanged.connect(self.on_calculate_tooldia)
+        self.vdia_entry.valueChanged.connect(self.on_calculate_tooldia)
+        self.vangle_entry.valueChanged.connect(self.on_calculate_tooldia)
+
+
     def ui_disconnect(self):
     def ui_disconnect(self):
         try:
         try:
             self.name_entry.editingFinished.disconnect(self.update_tree_name)
             self.name_entry.editingFinished.disconnect(self.update_tree_name)
         except (TypeError, AttributeError):
         except (TypeError, AttributeError):
             pass
             pass
 
 
+        try:
+            self.shape_combo.currentIndexChanged.disconnect(self.on_calculate_tooldia)
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.cutz_entry.valueChanged.disconnect(self.on_calculate_tooldia)
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.vdia_entry.valueChanged.disconnect(self.on_calculate_tooldia)
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            self.vangle_entry.valueChanged.disconnect(self.on_calculate_tooldia)
+        except (TypeError, AttributeError):
+            pass
+
         for key in self.form_fields:
         for key in self.form_fields:
             wdg = self.form_fields[key]
             wdg = self.form_fields[key]
 
 
@@ -2398,6 +2575,18 @@ class ToolsDB2(QtWidgets.QWidget):
             elif wdg_name == "gdb_p_contour":
             elif wdg_name == "gdb_p_contour":
                 self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val
                 self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val
 
 
+            # Isolation Tool
+            elif wdg_name == "gdb_i_passes":
+                self.db_tool_dict[tool_id]['data']['tools_iso_passes'] = val
+            elif wdg_name == "gdb_i_overlap":
+                self.db_tool_dict[tool_id]['data']['tools_iso_overlap'] = val
+            elif wdg_name == "gdb_i_milling_type":
+                self.db_tool_dict[tool_id]['data']['tools_iso_milling_type'] = val
+            elif wdg_name == "gdb_i_follow":
+                self.db_tool_dict[tool_id]['data']['tools_iso_follow'] = val
+            elif wdg_name == "gdb_i_iso_type":
+                self.db_tool_dict[tool_id]['data']['tools_iso_isotype'] = val
+
         self.callback_app()
         self.callback_app()
 
 
     def on_tool_requested_from_app(self):
     def on_tool_requested_from_app(self):

+ 1 - 1
AppEditors/FlatCAMExcEditor.py

@@ -2830,7 +2830,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
         self.app.ui.exc_edit_toolbar.setDisabled(False)
         self.app.ui.exc_edit_toolbar.setDisabled(False)
         self.app.ui.exc_edit_toolbar.setVisible(True)
         self.app.ui.exc_edit_toolbar.setVisible(True)
-        # self.app.ui.snap_toolbar.setDisabled(False)
+        # self.app.ui.status_toolbar.setDisabled(False)
 
 
         # start with GRID toolbar activated
         # start with GRID toolbar activated
         if self.app.ui.grid_snap_btn.isChecked() is False:
         if self.app.ui.grid_snap_btn.isChecked() is False:

+ 3 - 3
AppEditors/FlatCAMGeoEditor.py

@@ -3665,7 +3665,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.app.ui.geo_edit_toolbar.setDisabled(False)
         self.app.ui.geo_edit_toolbar.setDisabled(False)
         self.app.ui.geo_edit_toolbar.setVisible(True)
         self.app.ui.geo_edit_toolbar.setVisible(True)
 
 
-        self.app.ui.snap_toolbar.setDisabled(False)
+        self.app.ui.status_toolbar.setDisabled(False)
 
 
         self.app.ui.popmenu_disable.setVisible(False)
         self.app.ui.popmenu_disable.setVisible(False)
         self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
         self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
@@ -4135,11 +4135,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
 
         # make sure that the cursor shape is enabled/disabled, too
         # make sure that the cursor shape is enabled/disabled, too
         if self.options['grid_snap'] is True:
         if self.options['grid_snap'] is True:
-            self.app.inform.emit(_("Grid snap enabled."))
+            self.app.inform[str, bool].emit(_("Grid Snap enabled."), False)
             self.app.app_cursor.enabled = True
             self.app.app_cursor.enabled = True
         else:
         else:
             self.app.app_cursor.enabled = False
             self.app.app_cursor.enabled = False
-            self.app.inform.emit(_("Grid snap disabled."))
+            self.app.inform[str, bool].emit(_("Grid Snap disabled."), False)
 
 
     def on_canvas_click(self, event):
     def on_canvas_click(self, event):
         """
         """

+ 1 - 1
AppEditors/FlatCAMGrbEditor.py

@@ -3703,7 +3703,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
 
         self.app.ui.grb_edit_toolbar.setDisabled(False)
         self.app.ui.grb_edit_toolbar.setDisabled(False)
         self.app.ui.grb_edit_toolbar.setVisible(True)
         self.app.ui.grb_edit_toolbar.setVisible(True)
-        # self.app.ui.snap_toolbar.setDisabled(False)
+        # self.app.ui.status_toolbar.setDisabled(False)
 
 
         # start with GRID toolbar activated
         # start with GRID toolbar activated
         if self.app.ui.grid_snap_btn.isChecked() is False:
         if self.app.ui.grid_snap_btn.isChecked() is False:

+ 2 - 2
AppEditors/FlatCAMTextEditor.py

@@ -214,10 +214,10 @@ class TextEditor(QtWidgets.QWidget):
             filename = str(FCFileSaveDialog.get_saved_filename(
             filename = str(FCFileSaveDialog.get_saved_filename(
                 caption=_("Export Code ..."),
                 caption=_("Export Code ..."),
                 directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
                 directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
-                filter=_filter_
+                ext_filter=_filter_
             )[0])
             )[0])
         except TypeError:
         except TypeError:
-            filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), filter=_filter_)[0])
+            filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)[0])
 
 
         if filename == "":
         if filename == "":
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))

+ 36 - 12
AppGUI/GUIElements.py

@@ -570,10 +570,13 @@ class FCEntry3(FCEntry):
 
 
 
 
 class EvalEntry(QtWidgets.QLineEdit):
 class EvalEntry(QtWidgets.QLineEdit):
-    def __init__(self, parent=None):
+    def __init__(self, border_color=None, parent=None):
         super(EvalEntry, self).__init__(parent)
         super(EvalEntry, self).__init__(parent)
         self.readyToEdit = True
         self.readyToEdit = True
 
 
+        if border_color:
+            self.setStyleSheet("QLineEdit {border: 1px solid %s;}" % border_color)
+
         self.editingFinished.connect(self.on_edit_finished)
         self.editingFinished.connect(self.on_edit_finished)
 
 
     def on_edit_finished(self):
     def on_edit_finished(self):
@@ -639,7 +642,7 @@ class EvalEntry2(QtWidgets.QLineEdit):
 
 
     def get_value(self):
     def get_value(self):
         raw = str(self.text()).strip(' ')
         raw = str(self.text()).strip(' ')
-        evaled = 0.0
+
         try:
         try:
             evaled = eval(raw)
             evaled = eval(raw)
         except Exception as e:
         except Exception as e:
@@ -660,20 +663,20 @@ class NumericalEvalEntry(EvalEntry):
     """
     """
     Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
     Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
     """
     """
-    def __init__(self):
-        super().__init__()
+    def __init__(self, border_color=None):
+        super().__init__(border_color=border_color)
 
 
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s]*")
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s]*")
         validator = QtGui.QRegExpValidator(regex, self)
         validator = QtGui.QRegExpValidator(regex, self)
         self.setValidator(validator)
         self.setValidator(validator)
 
 
 
 
-class NumericalEvalTupleEntry(EvalEntry):
+class NumericalEvalTupleEntry(FCEntry):
     """
     """
     Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
     Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
     """
     """
-    def __init__(self):
-        super().__init__()
+    def __init__(self, border_color=None):
+        super().__init__(border_color=border_color)
 
 
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s\,]*")
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s\,]*")
         validator = QtGui.QRegExpValidator(regex, self)
         validator = QtGui.QRegExpValidator(regex, self)
@@ -2551,7 +2554,7 @@ class DialogBoxRadio(QtWidgets.QDialog):
         :param title: string with the window title
         :param title: string with the window title
         :param label: string with the message inside the dialog box
         :param label: string with the message inside the dialog box
         """
         """
-        super(DialogBoxRadio, self).__init__()
+        super(DialogBoxRadio, self).__init__(parent=parent)
         if initial_text is None:
         if initial_text is None:
             self.location = str((0, 0))
             self.location = str((0, 0))
         else:
         else:
@@ -2794,9 +2797,12 @@ class MyCompleter(QCompleter):
     insertText = QtCore.pyqtSignal(str)
     insertText = QtCore.pyqtSignal(str)
 
 
     def __init__(self, parent=None):
     def __init__(self, parent=None):
-        QCompleter.__init__(self)
+        QCompleter.__init__(self, parent=parent)
         self.setCompletionMode(QCompleter.PopupCompletion)
         self.setCompletionMode(QCompleter.PopupCompletion)
         self.highlighted.connect(self.setHighlighted)
         self.highlighted.connect(self.setHighlighted)
+
+        self.lastSelected = ''
+
         # self.popup().installEventFilter(self)
         # self.popup().installEventFilter(self)
 
 
     # def eventFilter(self, obj, event):
     # def eventFilter(self, obj, event):
@@ -2952,9 +2958,9 @@ class FCFileSaveDialog(QtWidgets.QFileDialog):
         super(FCFileSaveDialog, self).__init__(*args)
         super(FCFileSaveDialog, self).__init__(*args)
 
 
     @staticmethod
     @staticmethod
-    def get_saved_filename(parent=None, caption='', directory='', filter='', initialFilter=''):
+    def get_saved_filename(parent=None, caption='', directory='', ext_filter='', initialFilter=''):
         filename, _filter = QtWidgets.QFileDialog.getSaveFileName(parent=parent, caption=caption,
         filename, _filter = QtWidgets.QFileDialog.getSaveFileName(parent=parent, caption=caption,
-                                                                  directory=directory, filter=filter,
+                                                                  directory=directory, filter=ext_filter,
                                                                   initialFilter=initialFilter)
                                                                   initialFilter=initialFilter)
 
 
         filename = str(filename)
         filename = str(filename)
@@ -2970,6 +2976,22 @@ class FCFileSaveDialog(QtWidgets.QFileDialog):
             return filename, _filter
             return filename, _filter
 
 
 
 
+class FCDock(QtWidgets.QDockWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(FCDock, self).__init__(*args)
+        self.close_callback = kwargs["close_callback"] if "close_callback" in kwargs else None
+
+    def closeEvent(self, event: QtGui.QCloseEvent) -> None:
+        self.close_callback()
+        super().closeEvent(event)
+
+    def show(self) -> None:
+        if self.isFloating():
+            self.setFloating(False)
+        super().show()
+
+
 class FlatCAMActivityView(QtWidgets.QWidget):
 class FlatCAMActivityView(QtWidgets.QWidget):
     """
     """
     This class create and control the activity icon displayed in the App status bar
     This class create and control the activity icon displayed in the App status bar
@@ -3000,7 +3022,7 @@ class FlatCAMActivityView(QtWidgets.QWidget):
         self.movie_path = movie
         self.movie_path = movie
         self.icon_path = icon
         self.icon_path = icon
 
 
-        self.icon = QtWidgets.QLabel(self)
+        self.icon = FCLabel(self)
         self.icon.setGeometry(0, 0, 16, 12)
         self.icon.setGeometry(0, 0, 16, 12)
         self.movie = QtGui.QMovie(self.movie_path)
         self.movie = QtGui.QMovie(self.movie_path)
 
 
@@ -3019,6 +3041,8 @@ class FlatCAMActivityView(QtWidgets.QWidget):
 
 
         layout.addWidget(self.text)
         layout.addWidget(self.text)
 
 
+        self.icon.clicked.connect(self.app.on_toolbar_replot)
+
     def set_idle(self):
     def set_idle(self):
         self.movie.stop()
         self.movie.stop()
         self.text.setText(_("Idle."))
         self.text.setText(_("Idle."))

+ 144 - 109
AppGUI/MainGUI.py

@@ -58,17 +58,6 @@ class MainGUI(QtWidgets.QMainWindow):
         # ############ BUILDING THE GUI IS EXECUTED HERE ########################
         # ############ BUILDING THE GUI IS EXECUTED HERE ########################
         # #######################################################################
         # #######################################################################
 
 
-        # #######################################################################
-        # ####################### TCL Shell DOCK ################################
-        # #######################################################################
-        self.shell_dock = QtWidgets.QDockWidget("FlatCAM TCL Shell")
-        self.shell_dock.setObjectName('Shell_DockWidget')
-        self.shell_dock.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
-        self.shell_dock.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
-                                    QtWidgets.QDockWidget.DockWidgetFloatable |
-                                    QtWidgets.QDockWidget.DockWidgetClosable)
-        self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.shell_dock)
-
         # #######################################################################
         # #######################################################################
         # ###################### Menu BUILDING ##################################
         # ###################### Menu BUILDING ##################################
         # #######################################################################
         # #######################################################################
@@ -200,9 +189,9 @@ class MainGUI(QtWidgets.QMainWindow):
         self.menufilerunscript = QtWidgets.QAction(
         self.menufilerunscript = QtWidgets.QAction(
             QtGui.QIcon(self.app.resource_location + '/script16.png'), '%s\tShift+S' % _('Run Script ...'), self)
             QtGui.QIcon(self.app.resource_location + '/script16.png'), '%s\tShift+S' % _('Run Script ...'), self)
         self.menufilerunscript.setToolTip(
         self.menufilerunscript.setToolTip(
-           _("Will run the opened Tcl Script thus\n"
-             "enabling the automation of certain\n"
-             "functions of FlatCAM.")
+            _("Will run the opened Tcl Script thus\n"
+              "enabling the automation of certain\n"
+              "functions of FlatCAM.")
         )
         )
         self.menufile_scripting.addAction(self.menufilenewscript)
         self.menufile_scripting.addAction(self.menufilenewscript)
         self.menufile_scripting.addAction(self.menufileopenscript)
         self.menufile_scripting.addAction(self.menufileopenscript)
@@ -265,9 +254,9 @@ class MainGUI(QtWidgets.QMainWindow):
         self.menufileexportexcellon = QtWidgets.QAction(
         self.menufileexportexcellon = QtWidgets.QAction(
             QtGui.QIcon(self.app.resource_location + '/drill32.png'), _('Export &Excellon ...'), self)
             QtGui.QIcon(self.app.resource_location + '/drill32.png'), _('Export &Excellon ...'), self)
         self.menufileexportexcellon.setToolTip(
         self.menufileexportexcellon.setToolTip(
-           _("Will export an Excellon Object as Excellon file,\n"
-             "the coordinates format, the file units and zeros\n"
-             "are set in Preferences -> Excellon Export.")
+            _("Will export an Excellon Object as Excellon file,\n"
+              "the coordinates format, the file units and zeros\n"
+              "are set in Preferences -> Excellon Export.")
         )
         )
         self.menufileexport.addAction(self.menufileexportexcellon)
         self.menufileexport.addAction(self.menufileexportexcellon)
 
 
@@ -344,16 +333,16 @@ class MainGUI(QtWidgets.QMainWindow):
         self.menuedit_convertjoin = self.menuedit_convert.addAction(
         self.menuedit_convertjoin = self.menuedit_convert.addAction(
             QtGui.QIcon(self.app.resource_location + '/join16.png'), _('&Join Geo/Gerber/Exc -> Geo'))
             QtGui.QIcon(self.app.resource_location + '/join16.png'), _('&Join Geo/Gerber/Exc -> Geo'))
         self.menuedit_convertjoin.setToolTip(
         self.menuedit_convertjoin.setToolTip(
-           _("Merge a selection of objects, which can be of type:\n"
-             "- Gerber\n"
-             "- Excellon\n"
-             "- Geometry\n"
-             "into a new combo Geometry object.")
+            _("Merge a selection of objects, which can be of type:\n"
+              "- Gerber\n"
+              "- Excellon\n"
+              "- Geometry\n"
+              "into a new combo Geometry object.")
         )
         )
         self.menuedit_convertjoinexc = self.menuedit_convert.addAction(
         self.menuedit_convertjoinexc = self.menuedit_convert.addAction(
             QtGui.QIcon(self.app.resource_location + '/join16.png'), _('Join Excellon(s) -> Excellon'))
             QtGui.QIcon(self.app.resource_location + '/join16.png'), _('Join Excellon(s) -> Excellon'))
         self.menuedit_convertjoinexc.setToolTip(
         self.menuedit_convertjoinexc.setToolTip(
-           _("Merge a selection of Excellon objects into a new combo Excellon object.")
+            _("Merge a selection of Excellon objects into a new combo Excellon object.")
         )
         )
         self.menuedit_convertjoingrb = self.menuedit_convert.addAction(
         self.menuedit_convertjoingrb = self.menuedit_convert.addAction(
             QtGui.QIcon(self.app.resource_location + '/join16.png'), _('Join Gerber(s) -> Gerber'))
             QtGui.QIcon(self.app.resource_location + '/join16.png'), _('Join Gerber(s) -> Gerber'))
@@ -365,14 +354,14 @@ class MainGUI(QtWidgets.QMainWindow):
         self.menuedit_convert_sg2mg = self.menuedit_convert.addAction(
         self.menuedit_convert_sg2mg = self.menuedit_convert.addAction(
             QtGui.QIcon(self.app.resource_location + '/convert24.png'), _('Convert Single to MultiGeo'))
             QtGui.QIcon(self.app.resource_location + '/convert24.png'), _('Convert Single to MultiGeo'))
         self.menuedit_convert_sg2mg.setToolTip(
         self.menuedit_convert_sg2mg.setToolTip(
-           _("Will convert a Geometry object from single_geometry type\n"
-             "to a multi_geometry type.")
+            _("Will convert a Geometry object from single_geometry type\n"
+              "to a multi_geometry type.")
         )
         )
         self.menuedit_convert_mg2sg = self.menuedit_convert.addAction(
         self.menuedit_convert_mg2sg = self.menuedit_convert.addAction(
             QtGui.QIcon(self.app.resource_location + '/convert24.png'), _('Convert Multi to SingleGeo'))
             QtGui.QIcon(self.app.resource_location + '/convert24.png'), _('Convert Multi to SingleGeo'))
         self.menuedit_convert_mg2sg.setToolTip(
         self.menuedit_convert_mg2sg.setToolTip(
-           _("Will convert a Geometry object from multi_geometry type\n"
-             "to a single_geometry type.")
+            _("Will convert a Geometry object from multi_geometry type\n"
+              "to a single_geometry type.")
         )
         )
         # Separator
         # Separator
         self.menuedit_convert.addSeparator()
         self.menuedit_convert.addSeparator()
@@ -488,7 +477,7 @@ class MainGUI(QtWidgets.QMainWindow):
         self.menuview_toggle_grid = self.menuview.addAction(
         self.menuview_toggle_grid = self.menuview.addAction(
             QtGui.QIcon(self.app.resource_location + '/grid32.png'), _("&Toggle Grid Snap\tG"))
             QtGui.QIcon(self.app.resource_location + '/grid32.png'), _("&Toggle Grid Snap\tG"))
         self.menuview_toggle_grid_lines = self.menuview.addAction(
         self.menuview_toggle_grid_lines = self.menuview.addAction(
-            QtGui.QIcon(self.app.resource_location + '/grid32.png'), _("&Toggle Grid Lines\tAlt+G"))
+            QtGui.QIcon(self.app.resource_location + '/grid_lines32.png'), _("&Toggle Grid Lines\tAlt+G"))
         self.menuview_toggle_axis = self.menuview.addAction(
         self.menuview_toggle_axis = self.menuview.addAction(
             QtGui.QIcon(self.app.resource_location + '/axis32.png'), _("&Toggle Axis\tShift+G"))
             QtGui.QIcon(self.app.resource_location + '/axis32.png'), _("&Toggle Axis\tShift+G"))
         self.menuview_toggle_workspace = self.menuview.addAction(
         self.menuview_toggle_workspace = self.menuview.addAction(
@@ -825,10 +814,10 @@ class MainGUI(QtWidgets.QMainWindow):
         self.grb_edit_toolbar.setObjectName('GrbEditor_TB')
         self.grb_edit_toolbar.setObjectName('GrbEditor_TB')
         self.addToolBar(self.grb_edit_toolbar)
         self.addToolBar(self.grb_edit_toolbar)
 
 
-        self.snap_toolbar = QtWidgets.QToolBar(_('Grid Toolbar'))
-        self.snap_toolbar.setObjectName('Snap_TB')
-        # self.addToolBar(self.snap_toolbar)
-        self.snap_toolbar.setStyleSheet(
+        self.status_toolbar = QtWidgets.QToolBar(_('Grid Toolbar'))
+        self.status_toolbar.setObjectName('Snap_TB')
+        # self.addToolBar(self.status_toolbar)
+        self.status_toolbar.setStyleSheet(
             """
             """
             QToolBar { padding: 0; }
             QToolBar { padding: 0; }
             QToolBar QToolButton { padding: -2; margin: -2; }
             QToolBar QToolButton { padding: -2; margin: -2; }
@@ -1088,38 +1077,61 @@ class MainGUI(QtWidgets.QMainWindow):
         # ########################################################################
         # ########################################################################
 
 
         # Snap GRID toolbar is always active to facilitate usage of measurements done on GRID
         # Snap GRID toolbar is always active to facilitate usage of measurements done on GRID
-        self.grid_snap_btn = self.snap_toolbar.addAction(
+        self.grid_snap_btn = self.status_toolbar.addAction(
             QtGui.QIcon(self.app.resource_location + '/grid32.png'), _('Snap to grid'))
             QtGui.QIcon(self.app.resource_location + '/grid32.png'), _('Snap to grid'))
         self.grid_gap_x_entry = FCEntry2()
         self.grid_gap_x_entry = FCEntry2()
         self.grid_gap_x_entry.setMaximumWidth(70)
         self.grid_gap_x_entry.setMaximumWidth(70)
         self.grid_gap_x_entry.setToolTip(_("Grid X snapping distance"))
         self.grid_gap_x_entry.setToolTip(_("Grid X snapping distance"))
-        self.snap_toolbar.addWidget(self.grid_gap_x_entry)
+        self.status_toolbar.addWidget(self.grid_gap_x_entry)
+
+        self.status_toolbar.addWidget(QtWidgets.QLabel(" "))
+        self.grid_gap_link_cb = FCCheckBox()
+        self.grid_gap_link_cb.setToolTip(_("When active, value on Grid_X\n"
+                                           "is copied to the Grid_Y value."))
+        self.status_toolbar.addWidget(self.grid_gap_link_cb)
+        self.status_toolbar.addWidget(QtWidgets.QLabel(" "))
 
 
         self.grid_gap_y_entry = FCEntry2()
         self.grid_gap_y_entry = FCEntry2()
         self.grid_gap_y_entry.setMaximumWidth(70)
         self.grid_gap_y_entry.setMaximumWidth(70)
         self.grid_gap_y_entry.setToolTip(_("Grid Y snapping distance"))
         self.grid_gap_y_entry.setToolTip(_("Grid Y snapping distance"))
-        self.snap_toolbar.addWidget(self.grid_gap_y_entry)
+        self.status_toolbar.addWidget(self.grid_gap_y_entry)
 
 
-        self.grid_space_label = QtWidgets.QLabel("  ")
-        self.snap_toolbar.addWidget(self.grid_space_label)
-        self.grid_gap_link_cb = FCCheckBox()
-        self.grid_gap_link_cb.setToolTip(_("When active, value on Grid_X\n"
-                                         "is copied to the Grid_Y value."))
-        self.snap_toolbar.addWidget(self.grid_gap_link_cb)
+        self.status_toolbar.addWidget(QtWidgets.QLabel(" "))
+        self.axis_status_label = FCLabel()
+        self.axis_status_label.setToolTip(_("Toggle the display of axis on canvas"))
+        self.axis_status_label.setPixmap(QtGui.QPixmap(self.app.resource_location + '/axis16.png'))
+        self.status_toolbar.addWidget(self.axis_status_label)
+        self.status_toolbar.addWidget(QtWidgets.QLabel(" "))
+
+        self.shell_status_label = FCLabel()
+        self.shell_status_label.setToolTip(_("Command Line"))
+        self.shell_status_label.setPixmap(QtGui.QPixmap(self.app.resource_location + '/shell20.png'))
+        self.status_toolbar.addWidget(self.shell_status_label)
 
 
         self.ois_grid = OptionalInputSection(self.grid_gap_link_cb, [self.grid_gap_y_entry], logic=False)
         self.ois_grid = OptionalInputSection(self.grid_gap_link_cb, [self.grid_gap_y_entry], logic=False)
 
 
-        self.corner_snap_btn = self.snap_toolbar.addAction(
+        self.corner_snap_btn = self.status_toolbar.addAction(
             QtGui.QIcon(self.app.resource_location + '/corner32.png'), _('Snap to corner'))
             QtGui.QIcon(self.app.resource_location + '/corner32.png'), _('Snap to corner'))
 
 
         self.snap_max_dist_entry = FCEntry()
         self.snap_max_dist_entry = FCEntry()
         self.snap_max_dist_entry.setMaximumWidth(70)
         self.snap_max_dist_entry.setMaximumWidth(70)
         self.snap_max_dist_entry.setToolTip(_("Max. magnet distance"))
         self.snap_max_dist_entry.setToolTip(_("Max. magnet distance"))
-        self.snap_magnet = self.snap_toolbar.addWidget(self.snap_max_dist_entry)
+        self.snap_magnet = self.status_toolbar.addWidget(self.snap_max_dist_entry)
 
 
         self.corner_snap_btn.setVisible(False)
         self.corner_snap_btn.setVisible(False)
         self.snap_magnet.setVisible(False)
         self.snap_magnet.setVisible(False)
 
 
+        # #######################################################################
+        # ####################### TCL Shell DOCK ################################
+        # #######################################################################
+        self.shell_dock = FCDock("FlatCAM TCL Shell", close_callback=self.toggle_shell_ui)
+        self.shell_dock.setObjectName('Shell_DockWidget')
+        self.shell_dock.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
+        self.shell_dock.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
+                                    QtWidgets.QDockWidget.DockWidgetFloatable |
+                                    QtWidgets.QDockWidget.DockWidgetClosable)
+        self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.shell_dock)
+
         # ########################################################################
         # ########################################################################
         # ########################## Notebook # ##################################
         # ########################## Notebook # ##################################
         # ########################################################################
         # ########################################################################
@@ -1489,8 +1501,7 @@ class MainGUI(QtWidgets.QMainWindow):
             QtGui.QIcon(self.app.resource_location + '/resize16.png'), _("Resize Drill"))
             QtGui.QIcon(self.app.resource_location + '/resize16.png'), _("Resize Drill"))
 
 
         self.popMenu.addSeparator()
         self.popMenu.addSeparator()
-        self.popmenu_copy = self.popMenu.addAction(
-            QtGui.QIcon(self.app.resource_location + '/copy32.png'), _("Copy"))
+        self.popmenu_copy = self.popMenu.addAction(QtGui.QIcon(self.app.resource_location + '/copy32.png'), _("Copy"))
         self.popmenu_delete = self.popMenu.addAction(
         self.popmenu_delete = self.popMenu.addAction(
             QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete"))
             QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete"))
         self.popmenu_edit = self.popMenu.addAction(
         self.popmenu_edit = self.popMenu.addAction(
@@ -1513,19 +1524,19 @@ class MainGUI(QtWidgets.QMainWindow):
         self.infobar.addWidget(self.fcinfo, stretch=1)
         self.infobar.addWidget(self.fcinfo, stretch=1)
 
 
         # self.rel_position_label = QtWidgets.QLabel(
         # self.rel_position_label = QtWidgets.QLabel(
-        #     "<b>Dx</b>: 0.0000&nbsp;&nbsp;   <b>Dy</b>: 0.0000&nbsp;&nbsp;&nbsp;&nbsp;")
+        # "<b>Dx</b>: 0.0000&nbsp;&nbsp;   <b>Dy</b>: 0.0000&nbsp;&nbsp;&nbsp;&nbsp;")
         # self.rel_position_label.setMinimumWidth(110)
         # self.rel_position_label.setMinimumWidth(110)
         # self.rel_position_label.setToolTip(_("Relative measurement.\nReference is last click position"))
         # self.rel_position_label.setToolTip(_("Relative measurement.\nReference is last click position"))
         # self.infobar.addWidget(self.rel_position_label)
         # self.infobar.addWidget(self.rel_position_label)
         #
         #
-        self.position_label = QtWidgets.QLabel(
-            "&nbsp;<b>X</b>: 0.0000&nbsp;&nbsp;   <b>Y</b>: 0.0000&nbsp;")
+        self.position_label = QtWidgets.QLabel("&nbsp;<b>X</b>: 0.0000&nbsp;&nbsp;   <b>Y</b>: 0.0000&nbsp;")
         self.position_label.setMinimumWidth(110)
         self.position_label.setMinimumWidth(110)
-        self.position_label.setToolTip(_("Absolute measurement.\nReference is (X=0, Y= 0) position"))
+        self.position_label.setToolTip(_("Absolute measurement.\n"
+                                         "Reference is (X=0, Y= 0) position"))
         self.infobar.addWidget(self.position_label)
         self.infobar.addWidget(self.position_label)
 
 
-        self.snap_toolbar.setMaximumHeight(24)
-        self.infobar.addWidget(self.snap_toolbar)
+        self.status_toolbar.setMaximumHeight(24)
+        self.infobar.addWidget(self.status_toolbar)
 
 
         self.hud_label = FCLabel("H")
         self.hud_label = FCLabel("H")
         self.hud_label.setToolTip(_("HUD (Heads up display)"))
         self.hud_label.setToolTip(_("HUD (Heads up display)"))
@@ -1533,10 +1544,14 @@ class MainGUI(QtWidgets.QMainWindow):
         self.infobar.addWidget(self.hud_label)
         self.infobar.addWidget(self.hud_label)
 
 
         self.wplace_label = FCLabel("A4")
         self.wplace_label = FCLabel("A4")
+        self.wplace_label.setToolTip(_("Draw a delimiting rectangle on canvas.\n"
+                                       "The purpose is to illustrate the limits for our work.")
+                                     )
         self.wplace_label.setMargin(2)
         self.wplace_label.setMargin(2)
         self.infobar.addWidget(self.wplace_label)
         self.infobar.addWidget(self.wplace_label)
 
 
         self.units_label = QtWidgets.QLabel("[mm]")
         self.units_label = QtWidgets.QLabel("[mm]")
+        self.units_label.setToolTip(_("Application units"))
         self.units_label.setMargin(2)
         self.units_label.setMargin(2)
         self.infobar.addWidget(self.units_label)
         self.infobar.addWidget(self.units_label)
 
 
@@ -1655,7 +1670,7 @@ class MainGUI(QtWidgets.QMainWindow):
         self.clear_btn.clicked.connect(self.on_gui_clear)
         self.clear_btn.clicked.connect(self.on_gui_clear)
 
 
         self.wplace_label.clicked.connect(self.app.on_workspace_toggle)
         self.wplace_label.clicked.connect(self.app.on_workspace_toggle)
-        self.hud_label.clicked.connect(self.app.on_toggle_hud)
+        self.shell_status_label.clicked.connect(self.toggle_shell_ui)
 
 
         # to be used in the future
         # to be used in the future
         # self.plot_tab_area.tab_attached.connect(lambda x: print(x))
         # self.plot_tab_area.tab_attached.connect(lambda x: print(x))
@@ -1756,12 +1771,12 @@ class MainGUI(QtWidgets.QMainWindow):
             self.grb_edit_toolbar.setVisible(False)
             self.grb_edit_toolbar.setVisible(False)
 
 
         # if tb & 128:
         # if tb & 128:
-        #     self.ui.snap_toolbar.setVisible(True)
+        #     self.ui.status_toolbar.setVisible(True)
         # else:
         # else:
-        #     self.ui.snap_toolbar.setVisible(False)
+        #     self.ui.status_toolbar.setVisible(False)
 
 
         # Grid Toolbar is always active now
         # Grid Toolbar is always active now
-        self.snap_toolbar.setVisible(True)
+        self.status_toolbar.setVisible(True)
 
 
         if tb & 256:
         if tb & 256:
             self.toolbarshell.setVisible(True)
             self.toolbarshell.setVisible(True)
@@ -1811,6 +1826,8 @@ class MainGUI(QtWidgets.QMainWindow):
         msgbox.setText(_("Are you sure you want to delete the GUI Settings? \n"))
         msgbox.setText(_("Are you sure you want to delete the GUI Settings? \n"))
         msgbox.setWindowTitle(_("Clear GUI Settings"))
         msgbox.setWindowTitle(_("Clear GUI Settings"))
         msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/trash32.png'))
         msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/trash32.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
         bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
         bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
         bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
 
 
@@ -2235,11 +2252,11 @@ class MainGUI(QtWidgets.QMainWindow):
 
 
                 # Toggle axis
                 # Toggle axis
                 if key == QtCore.Qt.Key_G:
                 if key == QtCore.Qt.Key_G:
-                    self.app.on_toggle_axis()
+                    self.app.plotcanvas.on_toggle_axis()
 
 
                 # Toggle HUD (Heads-Up Display)
                 # Toggle HUD (Heads-Up Display)
                 if key == QtCore.Qt.Key_H:
                 if key == QtCore.Qt.Key_H:
-                    self.app.on_toggle_hud()
+                    self.app.plotcanvas.on_toggle_hud()
                 # Locate in Object
                 # Locate in Object
                 if key == QtCore.Qt.Key_J:
                 if key == QtCore.Qt.Key_J:
                     self.app.on_locate(obj=self.app.collection.get_active())
                     self.app.on_locate(obj=self.app.collection.get_active())
@@ -2318,7 +2335,7 @@ class MainGUI(QtWidgets.QMainWindow):
 
 
                 # Toggle Grid lines
                 # Toggle Grid lines
                 if key == QtCore.Qt.Key_G:
                 if key == QtCore.Qt.Key_G:
-                    self.app.on_toggle_grid_lines()
+                    self.app.plotcanvas.on_toggle_grid_lines()
                     return
                     return
 
 
                 # Punch Gerber Tool
                 # Punch Gerber Tool
@@ -2488,7 +2505,7 @@ class MainGUI(QtWidgets.QMainWindow):
                         if active_index == 0:
                         if active_index == 0:
                             self.app.collection.set_active(names_list[-1])
                             self.app.collection.set_active(names_list[-1])
                         else:
                         else:
-                            self.app.collection.set_active(names_list[active_index-1])
+                            self.app.collection.set_active(names_list[active_index - 1])
 
 
                 # Select the object in the Tree below the current one
                 # Select the object in the Tree below the current one
                 if key == QtCore.Qt.Key_Down:
                 if key == QtCore.Qt.Key_Down:
@@ -2503,7 +2520,7 @@ class MainGUI(QtWidgets.QMainWindow):
                         if active_index == len(names_list) - 1:
                         if active_index == len(names_list) - 1:
                             self.app.collection.set_active(names_list[0])
                             self.app.collection.set_active(names_list[0])
                         else:
                         else:
-                            self.app.collection.set_active(names_list[active_index+1])
+                            self.app.collection.set_active(names_list[active_index + 1])
 
 
                 # New Geometry
                 # New Geometry
                 if key == QtCore.Qt.Key_B:
                 if key == QtCore.Qt.Key_B:
@@ -2627,6 +2644,8 @@ class MainGUI(QtWidgets.QMainWindow):
                         messagebox.setText(msg)
                         messagebox.setText(msg)
                         messagebox.setWindowTitle(_("Warning"))
                         messagebox.setWindowTitle(_("Warning"))
                         messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
                         messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
+                        messagebox.setIcon(QtWidgets.QMessageBox.Question)
+
                         messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                         messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                         messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                         messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                         messagebox.exec_()
                         messagebox.exec_()
@@ -2789,6 +2808,8 @@ class MainGUI(QtWidgets.QMainWindow):
                             messagebox.setText(msg)
                             messagebox.setText(msg)
                             messagebox.setWindowTitle(_("Warning"))
                             messagebox.setWindowTitle(_("Warning"))
                             messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
                             messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
+                            messagebox.setIcon(QtWidgets.QMessageBox.Warning)
+
                             messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                             messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                             messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                             messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                             messagebox.exec_()
                             messagebox.exec_()
@@ -2834,6 +2855,8 @@ class MainGUI(QtWidgets.QMainWindow):
                             messagebox.setText(msg)
                             messagebox.setText(msg)
                             messagebox.setWindowTitle(_("Warning"))
                             messagebox.setWindowTitle(_("Warning"))
                             messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
                             messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
+                            messagebox.setIcon(QtWidgets.QMessageBox.Warning)
+
                             messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                             messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                             messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                             messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                             messagebox.exec_()
                             messagebox.exec_()
@@ -2854,6 +2877,8 @@ class MainGUI(QtWidgets.QMainWindow):
                             messagebox.setText(msg)
                             messagebox.setText(msg)
                             messagebox.setWindowTitle(_("Warning"))
                             messagebox.setWindowTitle(_("Warning"))
                             messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
                             messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
+                            messagebox.setIcon(QtWidgets.QMessageBox.Warning)
+
                             messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                             messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                             messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                             messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                             messagebox.exec_()
                             messagebox.exec_()
@@ -3569,7 +3594,7 @@ class MainGUI(QtWidgets.QMainWindow):
             # hide all Toolbars
             # hide all Toolbars
             for tb in self.findChildren(QtWidgets.QToolBar):
             for tb in self.findChildren(QtWidgets.QToolBar):
                 tb.setVisible(False)
                 tb.setVisible(False)
-            self.snap_toolbar.setVisible(True)   # This Toolbar is always visible so restore it
+            self.status_toolbar.setVisible(True)  # This Toolbar is always visible so restore it
 
 
             self.splitter.setSizes([0, 1])
             self.splitter.setSizes([0, 1])
             self.toggle_fscreen = True
             self.toggle_fscreen = True
@@ -3629,8 +3654,18 @@ class MainGUI(QtWidgets.QMainWindow):
         if self.shell_dock.isVisible():
         if self.shell_dock.isVisible():
             self.shell_dock.hide()
             self.shell_dock.hide()
             self.app.plotcanvas.native.setFocus()
             self.app.plotcanvas.native.setFocus()
+            self.shell_status_label.setStyleSheet("")
+            self.app.inform[str, bool].emit(_("Shell disabled."), False)
         else:
         else:
             self.shell_dock.show()
             self.shell_dock.show()
+            self.shell_status_label.setStyleSheet("""
+                                                  QLabel
+                                                  {
+                                                      color: black;
+                                                      background-color: lightcoral;
+                                                  }
+                                                  """)
+            self.app.inform[str, bool].emit(_("Shell enabled."), False)
 
 
             # I want to take the focus and give it to the Tcl Shell when the Tcl Shell is run
             # I want to take the focus and give it to the Tcl Shell when the Tcl Shell is run
             # self.shell._edit.setFocus()
             # self.shell._edit.setFocus()
@@ -3671,7 +3706,7 @@ class ShortcutsTab(QtWidgets.QWidget):
         self.sh_tab_layout.addLayout(self.sh_hlay)
         self.sh_tab_layout.addLayout(self.sh_hlay)
 
 
         self.app_sh_msg = (
         self.app_sh_msg = (
-            '''<b>%s</b><br>
+                '''<b>%s</b><br>
             <table border="0" cellpadding="0" cellspacing="0" style="width:283px">
             <table border="0" cellpadding="0" cellspacing="0" style="width:283px">
                 <tbody>
                 <tbody>
                     <tr height="20">
                     <tr height="20">
@@ -4040,53 +4075,53 @@ class ShortcutsTab(QtWidgets.QWidget):
                 </tbody>
                 </tbody>
             </table>
             </table>
             ''' %
             ''' %
-            (
-                _("General Shortcut list"),
-                _("SHOW SHORTCUT LIST"), _("Switch to Project Tab"), _("Switch to Selected Tab"),
-                _("Switch to Tool Tab"),
-                _("New Gerber"), _("Edit Object (if selected)"), _("Grid On/Off"), _("Jump to Coordinates"),
-                _("New Excellon"), _("Move Obj"), _("New Geometry"), _("Set Origin"), _("Change Units"),
-                _("Open Properties Tool"), _("Rotate by 90 degree CW"), _("Shell Toggle"),
-                _("Add a Tool (when in Geometry Selected Tab or in Tools NCC or Tools Paint)"), _("Zoom Fit"),
-                _("Flip on X_axis"), _("Flip on Y_axis"), _("Zoom Out"), _("Zoom In"),
-
-                # CTRL section
-                _("Select All"), _("Copy Obj"), _("Open Tools Database"),
-                _("Open Excellon File"), _("Open Gerber File"), _("Distance Tool"), _("New Project"),
-                _("Open Project"), _("Print (PDF)"), _("PDF Import Tool"), _("Save Project"), _("Toggle Plot Area"),
-
-                # SHIFT section
-                _("Copy Obj_Name"),
-                _("Toggle Code Editor"), _("Toggle the axis"), _("Locate in Object"), _("Distance Minimum Tool"),
-                _("Open Preferences Window"),
-                _("Rotate by 90 degree CCW"), _("Run a Script"), _("Toggle the workspace"), _("Skew on X axis"),
-                _("Skew on Y axis"),
-
-                # ALT section
-                _("Align Objects Tool"), _("Calculators Tool"), _("2-Sided PCB Tool"), _("Extract Drills Tool"),
-                _("Fiducials Tool"), _("Toggle Grid Lines"),
-                _("Punch Gerber Tool"), _("Isolation Tool"), _("Copper Thieving Tool"),
-                _("Solder Paste Dispensing Tool"),
-                _("Film PCB Tool"), _("Corner Markers Tool"), _("Non-Copper Clearing Tool"), _("Optimal Tool"),
-                _("Paint Area Tool"), _("QRCode Tool"), _("Rules Check Tool"),
-                _("View File Source"), _("Transformations Tool"),
-                _("Subtract Tool"), _("Cutout PCB Tool"), _("Panelize PCB"),
-                _("Enable all Plots"), _("Disable all Plots"), _("Disable Non-selected Plots"),
-                _("Toggle Full Screen"),
-
-                # CTRL + ALT section
-                _("Abort current task (gracefully)"),
-
-                # CTRL + SHIFT section
-                _("Save Project As"),
-                _("Paste Special. Will convert a Windows path style to the one required in Tcl Shell"),
-
-                # F keys section
-                _("Open Online Manual"),
-                _("Open Online Tutorials"), _("Refresh Plots"), _("Delete Object"), _("Alternate: Delete Tool"),
-                _("(left to Key_1)Toggle Notebook Area (Left Side)"), _("En(Dis)able Obj Plot"),
-                _("Deselects all objects")
-            )
+                (
+                    _("General Shortcut list"),
+                    _("SHOW SHORTCUT LIST"), _("Switch to Project Tab"), _("Switch to Selected Tab"),
+                    _("Switch to Tool Tab"),
+                    _("New Gerber"), _("Edit Object (if selected)"), _("Grid On/Off"), _("Jump to Coordinates"),
+                    _("New Excellon"), _("Move Obj"), _("New Geometry"), _("Set Origin"), _("Change Units"),
+                    _("Open Properties Tool"), _("Rotate by 90 degree CW"), _("Shell Toggle"),
+                    _("Add a Tool (when in Geometry Selected Tab or in Tools NCC or Tools Paint)"), _("Zoom Fit"),
+                    _("Flip on X_axis"), _("Flip on Y_axis"), _("Zoom Out"), _("Zoom In"),
+
+                    # CTRL section
+                    _("Select All"), _("Copy Obj"), _("Open Tools Database"),
+                    _("Open Excellon File"), _("Open Gerber File"), _("Distance Tool"), _("New Project"),
+                    _("Open Project"), _("Print (PDF)"), _("PDF Import Tool"), _("Save Project"), _("Toggle Plot Area"),
+
+                    # SHIFT section
+                    _("Copy Obj_Name"),
+                    _("Toggle Code Editor"), _("Toggle the axis"), _("Locate in Object"), _("Distance Minimum Tool"),
+                    _("Open Preferences Window"),
+                    _("Rotate by 90 degree CCW"), _("Run a Script"), _("Toggle the workspace"), _("Skew on X axis"),
+                    _("Skew on Y axis"),
+
+                    # ALT section
+                    _("Align Objects Tool"), _("Calculators Tool"), _("2-Sided PCB Tool"), _("Extract Drills Tool"),
+                    _("Fiducials Tool"), _("Toggle Grid Lines"),
+                    _("Punch Gerber Tool"), _("Isolation Tool"), _("Copper Thieving Tool"),
+                    _("Solder Paste Dispensing Tool"),
+                    _("Film PCB Tool"), _("Corner Markers Tool"), _("Non-Copper Clearing Tool"), _("Optimal Tool"),
+                    _("Paint Area Tool"), _("QRCode Tool"), _("Rules Check Tool"),
+                    _("View File Source"), _("Transformations Tool"),
+                    _("Subtract Tool"), _("Cutout PCB Tool"), _("Panelize PCB"),
+                    _("Enable all Plots"), _("Disable all Plots"), _("Disable Non-selected Plots"),
+                    _("Toggle Full Screen"),
+
+                    # CTRL + ALT section
+                    _("Abort current task (gracefully)"),
+
+                    # CTRL + SHIFT section
+                    _("Save Project As"),
+                    _("Paste Special. Will convert a Windows path style to the one required in Tcl Shell"),
+
+                    # F keys section
+                    _("Open Online Manual"),
+                    _("Open Online Tutorials"), _("Refresh Plots"), _("Delete Object"), _("Alternate: Delete Tool"),
+                    _("(left to Key_1)Toggle Notebook Area (Left Side)"), _("En(Dis)able Obj Plot"),
+                    _("Deselects all objects")
+                )
         )
         )
 
 
         self.sh_app = QtWidgets.QTextEdit()
         self.sh_app = QtWidgets.QTextEdit()

+ 89 - 316
AppGUI/ObjectUI.py

@@ -114,7 +114,7 @@ class ObjectUI(QtWidgets.QWidget):
             self.common_grid.addWidget(self.transform_label, 2, 0, 1, 2)
             self.common_grid.addWidget(self.transform_label, 2, 0, 1, 2)
 
 
             # ### Scale ####
             # ### Scale ####
-            self.scale_entry = NumericalEvalEntry()
+            self.scale_entry = NumericalEvalEntry(border_color='#0069A9')
             self.scale_entry.set_value(1.0)
             self.scale_entry.set_value(1.0)
             self.scale_entry.setToolTip(
             self.scale_entry.setToolTip(
                 _("Factor by which to multiply\n"
                 _("Factor by which to multiply\n"
@@ -132,7 +132,7 @@ class ObjectUI(QtWidgets.QWidget):
             self.common_grid.addWidget(self.scale_button, 3, 1)
             self.common_grid.addWidget(self.scale_button, 3, 1)
 
 
             # ### Offset ####
             # ### Offset ####
-            self.offsetvector_entry = NumericalEvalTupleEntry()
+            self.offsetvector_entry = NumericalEvalTupleEntry(border_color='#0069A9')
             self.offsetvector_entry.setText("(0.0, 0.0)")
             self.offsetvector_entry.setText("(0.0, 0.0)")
             self.offsetvector_entry.setToolTip(
             self.offsetvector_entry.setToolTip(
                 _("Amount by which to move the object\n"
                 _("Amount by which to move the object\n"
@@ -153,17 +153,20 @@ class ObjectUI(QtWidgets.QWidget):
     
     
     def confirmation_message(self, accepted, minval, maxval):
     def confirmation_message(self, accepted, minval, maxval):
         if accepted is False:
         if accepted is False:
-            self.app.inform.emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' %
-                                 (_("Edited value is out of range"), self.decimals, minval, self.decimals, maxval))
+            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:
         else:
-            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
 
     def confirmation_message_int(self, accepted, minval, maxval):
     def confirmation_message_int(self, accepted, minval, maxval):
         if accepted is False:
         if accepted is False:
-            self.app.inform.emit('[WARNING_NOTCL] %s: [%d, %d]' %
-                                 (_("Edited value is out of range"), minval, maxval))
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                 (_("Edited value is out of range"), minval, maxval), False)
         else:
         else:
-            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
 
 
 
 class GerberObjectUI(ObjectUI):
 class GerberObjectUI(ObjectUI):
@@ -205,27 +208,37 @@ class GerberObjectUI(ObjectUI):
         self.multicolored_cb.setMinimumWidth(55)
         self.multicolored_cb.setMinimumWidth(55)
         grid0.addWidget(self.multicolored_cb, 0, 2)
         grid0.addWidget(self.multicolored_cb, 0, 2)
 
 
-        # Plot CB
-        self.plot_cb = FCCheckBox('%s' % _("Plot"))
-        # self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
-        self.plot_cb.setToolTip(_("Plot (show) this object."))
-
-        grid0.addWidget(self.plot_cb, 1, 0, 1, 2)
-
         # ## Object name
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.name_hlay = QtWidgets.QHBoxLayout()
-        self.custom_box.addLayout(self.name_hlay)
+        grid0.addLayout(self.name_hlay, 1, 0, 1, 3)
+
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_hlay.addWidget(name_label)
         self.name_hlay.addWidget(name_label)
         self.name_hlay.addWidget(self.name_entry)
         self.name_hlay.addWidget(self.name_entry)
 
 
+        # Plot CB
+        self.plot_lbl = FCLabel('%s:' % _("Plot"))
+        self.plot_lbl.setToolTip(_("Plot (show) this object."))
+        self.plot_cb = FCCheckBox()
+
+        grid0.addWidget(self.plot_lbl, 2, 0)
+        grid0.addWidget(self.plot_cb, 2, 1)
+
+        # generate follow
+        self.follow_cb = FCCheckBox('%s' % _("Follow"))
+        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.setMinimumWidth(55)
+        grid0.addWidget(self.follow_cb, 2, 2)
+
         hlay_plot = QtWidgets.QHBoxLayout()
         hlay_plot = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(hlay_plot)
         self.custom_box.addLayout(hlay_plot)
 
 
         # ### Gerber Apertures ####
         # ### Gerber Apertures ####
-        self.apertures_table_label = QtWidgets.QLabel('<b>%s:</b>' % _('Apertures'))
+        self.apertures_table_label = QtWidgets.QLabel('%s:' % _('Apertures'))
         self.apertures_table_label.setToolTip(
         self.apertures_table_label.setToolTip(
             _("Apertures Table for the Gerber Object.")
             _("Apertures Table for the Gerber Object.")
         )
         )
@@ -282,253 +295,7 @@ class GerberObjectUI(ObjectUI):
         # start with apertures table hidden
         # start with apertures table hidden
         self.apertures_table.setVisible(False)
         self.apertures_table.setVisible(False)
 
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.custom_box.addWidget(separator_line)
-
-        # Isolation Routing
-        self.isolation_routing_label = QtWidgets.QLabel("<b>%s</b>" % _("Isolation Routing"))
-        self.isolation_routing_label.setToolTip(
-            _("Create a Geometry object with\n"
-              "toolpaths to cut outside polygons.")
-        )
-        self.custom_box.addWidget(self.isolation_routing_label)
-
-        # ###########################################
-        # ########## NEW GRID #######################
-        # ###########################################
-
-        grid1 = QtWidgets.QGridLayout()
-        self.custom_box.addLayout(grid1)
-        grid1.setColumnStretch(0, 0)
-        grid1.setColumnStretch(1, 1)
-        grid1.setColumnStretch(2, 1)
-
-        # Tool Type
-        self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
-        self.tool_type_label.setToolTip(
-            _("Choose which tool to use for Gerber isolation:\n"
-              "'Circular' or 'V-shape'.\n"
-              "When the 'V-shape' is selected then the tool\n"
-              "diameter will depend on the chosen cut depth.")
-        )
-        self.tool_type_radio = RadioSet([{'label': _('Circular'), 'value': 'circular'},
-                                         {'label': _('V-Shape'), 'value': 'v'}])
-
-        grid1.addWidget(self.tool_type_label, 0, 0)
-        grid1.addWidget(self.tool_type_radio, 0, 1, 1, 2)
-
-        # Tip Dia
-        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
-        self.tipdialabel.setToolTip(
-            _("The tip diameter for V-Shape Tool")
-        )
-        self.tipdia_spinner = FCDoubleSpinner(callback=self.confirmation_message)
-        self.tipdia_spinner.set_range(-99.9999, 99.9999)
-        self.tipdia_spinner.set_precision(self.decimals)
-        self.tipdia_spinner.setSingleStep(0.1)
-        self.tipdia_spinner.setWrapping(True)
-        grid1.addWidget(self.tipdialabel, 1, 0)
-        grid1.addWidget(self.tipdia_spinner, 1, 1, 1, 2)
-
-        # 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_spinner = FCDoubleSpinner(callback=self.confirmation_message)
-        self.tipangle_spinner.set_range(1, 180)
-        self.tipangle_spinner.set_precision(self.decimals)
-        self.tipangle_spinner.setSingleStep(5)
-        self.tipangle_spinner.setWrapping(True)
-        grid1.addWidget(self.tipanglelabel, 2, 0)
-        grid1.addWidget(self.tipangle_spinner, 2, 1, 1, 2)
-
-        # Cut Z
-        self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
-        self.cutzlabel.setToolTip(
-            _("Cutting depth (negative)\n"
-              "below the copper surface.")
-        )
-        self.cutz_spinner = FCDoubleSpinner(callback=self.confirmation_message)
-        self.cutz_spinner.set_range(-9999.9999, 0.0000)
-        self.cutz_spinner.set_precision(self.decimals)
-        self.cutz_spinner.setSingleStep(0.1)
-        self.cutz_spinner.setWrapping(True)
-        grid1.addWidget(self.cutzlabel, 3, 0)
-        grid1.addWidget(self.cutz_spinner, 3, 1, 1, 2)
-
-        # Tool diameter
-        tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
-        tdlabel.setToolTip(
-            _("Diameter of the cutting tool.\n"
-              "If you want to have an isolation path\n"
-              "inside the actual shape of the Gerber\n"
-              "feature, use a negative value for\n"
-              "this parameter.")
-        )
-        tdlabel.setMinimumWidth(90)
-        self.iso_tool_dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.iso_tool_dia_entry.set_range(-9999.9999, 9999.9999)
-        self.iso_tool_dia_entry.set_precision(self.decimals)
-        self.iso_tool_dia_entry.setSingleStep(0.1)
-
-        grid1.addWidget(tdlabel, 4, 0)
-        grid1.addWidget(self.iso_tool_dia_entry, 4, 1, 1, 2)
-
-        # Number of Passes
-        passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
-        passlabel.setToolTip(
-            _("Width of the isolation gap in\n"
-              "number (integer) of tool widths.")
-        )
-        passlabel.setMinimumWidth(90)
-        self.iso_width_entry = FCSpinner(callback=self.confirmation_message_int)
-        self.iso_width_entry.set_range(1, 999)
-
-        grid1.addWidget(passlabel, 5, 0)
-        grid1.addWidget(self.iso_width_entry, 5, 1, 1, 2)
-
-        # Pass overlap
-        overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
-        overlabel.setToolTip(
-            _("How much (percentage) of the tool width to overlap each tool pass.")
-        )
-        overlabel.setMinimumWidth(90)
-        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)
-        grid1.addWidget(overlabel, 6, 0)
-        grid1.addWidget(self.iso_overlap_entry, 6, 1, 1, 2)
-
-        # Milling Type Radio Button
-        self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
-        self.milling_type_label.setToolTip(
-            _("Milling type:\n"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conventional'), 'value': 'cv'}])
-        grid1.addWidget(self.milling_type_label, 7, 0)
-        grid1.addWidget(self.milling_type_radio, 7, 1, 1, 2)
-
-        # combine all passes CB
-        self.combine_passes_cb = FCCheckBox(label=_('Combine'))
-        self.combine_passes_cb.setToolTip(
-            _("Combine all passes into one object")
-        )
-
-        # generate follow
-        self.follow_cb = FCCheckBox(label=_('"Follow"'))
-        self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
-                                    "This means that it will cut through\n"
-                                    "the middle of the trace."))
-
-        # avoid an area from isolation
-        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."))
-        grid1.addWidget(self.combine_passes_cb, 8, 0)
-        grid1.addWidget(self.follow_cb, 8, 1)
-        grid1.addWidget(self.except_cb, 8, 2)
-
-        # ## Form Layout
-        form_layout = QtWidgets.QFormLayout()
-        grid1.addLayout(form_layout, 9, 0, 1, 3)
-
-        # ################################################
-        # ##### Type of object to be excepted ############
-        # ################################################
-        self.type_obj_combo = FCComboBox()
-        self.type_obj_combo.addItems([_("Gerber"), _("Geometry")])
-
-        # we get rid of item1 ("Excellon") as it is not suitable
-        # self.type_obj_combo.view().setRowHidden(1, True)
-        self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.resource_loc + "/flatcam_icon16.png"))
-        self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.resource_loc + "/geometry16.png"))
-
-        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Obj Type"))
-        self.type_obj_combo_label.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.")
-        )
-        # self.type_obj_combo_label.setMinimumWidth(60)
-        form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
-
-        # ################################################
-        # ##### The object to be excepted ################
-        # ################################################
-        self.obj_combo = FCComboBox()
-
-        self.obj_label = QtWidgets.QLabel('%s:' % _("Object"))
-        self.obj_label.setToolTip(_("Object whose area will be removed from isolation geometry."))
-
-        form_layout.addRow(self.obj_label, self.obj_combo)
-
-        # ---------------------------------------------- #
-        # --------- Isolation scope -------------------- #
-        # ---------------------------------------------- #
-        self.iso_scope_label = QtWidgets.QLabel('<b>%s:</b>' % _('Scope'))
-        self.iso_scope_label.setToolTip(
-            _("Isolation scope. Choose what to isolate:\n"
-              "- 'All' -> Isolate all the polygons in the object\n"
-              "- 'Selection' -> Isolate a selection of polygons.")
-        )
-        self.iso_scope_radio = RadioSet([{'label': _('All'), 'value': 'all'},
-                                         {'label': _('Selection'), 'value': 'single'}])
-
-        grid1.addWidget(self.iso_scope_label, 10, 0)
-        grid1.addWidget(self.iso_scope_radio, 10, 1, 1, 2)
-
-        # ---------------------------------------------- #
-        # --------- Isolation type  -------------------- #
-        # ---------------------------------------------- #
-        self.iso_type_label = QtWidgets.QLabel('<b>%s:</b>' % _('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'}])
-
-        grid1.addWidget(self.iso_type_label, 11, 0)
-        grid1.addWidget(self.iso_type_radio, 11, 1, 1, 2)
-
-        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.")
-        )
-        grid1.addWidget(self.generate_iso_button, 12, 0, 1, 3)
-
+        # Buffer Geometry
         self.create_buffer_button = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
         self.create_buffer_button = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
         self.create_buffer_button.setToolTip(
         self.create_buffer_button.setToolTip(
             _("This button is shown only when the Gerber file\n"
             _("This button is shown only when the Gerber file\n"
@@ -536,19 +303,12 @@ class GerberObjectUI(ObjectUI):
               "Clicking this will create the buffered geometry\n"
               "Clicking this will create the buffered geometry\n"
               "required for isolation.")
               "required for isolation.")
         )
         )
-        grid1.addWidget(self.create_buffer_button, 13, 0, 1, 3)
-
-        self.ohis_iso = OptionalHideInputSection(
-            self.except_cb,
-            [self.type_obj_combo, self.type_obj_combo_label, self.obj_combo, self.obj_label],
-            logic=True
-        )
+        self.custom_box.addWidget(self.create_buffer_button)
 
 
-        separator_line2 = QtWidgets.QFrame()
-        separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid1.addWidget(separator_line2, 14, 0, 1, 3)
-        # grid1.addWidget(QtWidgets.QLabel(''), 15, 0)
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.custom_box.addWidget(separator_line)
 
 
         # ###########################################
         # ###########################################
         # ########## NEW GRID #######################
         # ########## NEW GRID #######################
@@ -562,14 +322,7 @@ class GerberObjectUI(ObjectUI):
         self.tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("TOOLS"))
         self.tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("TOOLS"))
         grid2.addWidget(self.tool_lbl, 0, 0, 1, 2)
         grid2.addWidget(self.tool_lbl, 0, 0, 1, 2)
 
 
-        # ## Isolation Routing
-        self.iso_label = QtWidgets.QLabel("%s" % _("Isolation"))
-        self.iso_label.setToolTip(
-            _("Create a Geometry object with\n"
-              "toolpaths to cut around polygons.")
-        )
-        self.iso_label.setMinimumWidth(90)
-
+        # Isolation Tool - will create isolation paths around the copper features
         self.iso_button = QtWidgets.QPushButton(_('Isolation Routing'))
         self.iso_button = QtWidgets.QPushButton(_('Isolation Routing'))
         self.iso_button.setToolTip(
         self.iso_button.setToolTip(
             _("Create a Geometry object with\n"
             _("Create a Geometry object with\n"
@@ -581,17 +334,9 @@ class GerberObjectUI(ObjectUI):
                                           font-weight: bold;
                                           font-weight: bold;
                                       }
                                       }
                                       """)
                                       """)
-        grid2.addWidget(self.iso_label, 1, 0)
-        grid2.addWidget(self.iso_button, 1, 1)
+        grid2.addWidget(self.iso_button, 1, 0, 1, 2)
 
 
         # ## Clear non-copper regions
         # ## Clear non-copper regions
-        self.clearcopper_label = QtWidgets.QLabel("%s" % _("Clear N-copper"))
-        self.clearcopper_label.setToolTip(
-            _("Create a Geometry object with\n"
-              "toolpaths to cut all non-copper regions.")
-        )
-        self.clearcopper_label.setMinimumWidth(90)
-
         self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool'))
         self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool'))
         self.generate_ncc_button.setToolTip(
         self.generate_ncc_button.setToolTip(
             _("Create the Geometry Object\n"
             _("Create the Geometry Object\n"
@@ -603,17 +348,9 @@ class GerberObjectUI(ObjectUI):
                             font-weight: bold;
                             font-weight: bold;
                         }
                         }
                         """)
                         """)
-        grid2.addWidget(self.clearcopper_label, 2, 0)
-        grid2.addWidget(self.generate_ncc_button, 2, 1)
+        grid2.addWidget(self.generate_ncc_button, 2, 0, 1, 2)
 
 
         # ## Board cutout
         # ## Board cutout
-        self.board_cutout_label = QtWidgets.QLabel("%s" % _("Board cutout"))
-        self.board_cutout_label.setToolTip(
-            _("Create toolpaths to cut around\n"
-              "the PCB and separate it from\n"
-              "the original board.")
-        )
-
         self.generate_cutout_button = QtWidgets.QPushButton(_('Cutout Tool'))
         self.generate_cutout_button = QtWidgets.QPushButton(_('Cutout Tool'))
         self.generate_cutout_button.setToolTip(
         self.generate_cutout_button.setToolTip(
             _("Generate the geometry for\n"
             _("Generate the geometry for\n"
@@ -625,8 +362,7 @@ class GerberObjectUI(ObjectUI):
                             font-weight: bold;
                             font-weight: bold;
                         }
                         }
                         """)
                         """)
-        grid2.addWidget(self.board_cutout_label, 3, 0)
-        grid2.addWidget(self.generate_cutout_button, 3, 1)
+        grid2.addWidget(self.generate_cutout_button, 3, 0, 1, 2)
 
 
         separator_line = QtWidgets.QFrame()
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -744,22 +480,38 @@ class ExcellonObjectUI(ObjectUI):
                           parent=parent,
                           parent=parent,
                           app=self.app)
                           app=self.app)
 
 
-        # ### Plot options ####
-        hlay_plot = QtWidgets.QHBoxLayout()
-        self.custom_box.addLayout(hlay_plot)
+        # Plot options
+        grid_h = QtWidgets.QGridLayout()
+        grid_h.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.custom_box.addLayout(grid_h)
+        grid_h.setColumnStretch(0, 0)
+        grid_h.setColumnStretch(1, 1)
 
 
         self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
         self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
+        self.plot_options_label.setMinimumWidth(90)
+
+        grid_h.addWidget(self.plot_options_label, 0, 0)
+
+        # Solid CB
         self.solid_cb = FCCheckBox(label=_('Solid'))
         self.solid_cb = FCCheckBox(label=_('Solid'))
         self.solid_cb.setToolTip(
         self.solid_cb.setToolTip(
             _("Solid circles.")
             _("Solid circles.")
         )
         )
-        hlay_plot.addWidget(self.plot_options_label)
-        hlay_plot.addStretch()
-        hlay_plot.addWidget(self.solid_cb)
+        self.solid_cb.setMinimumWidth(50)
+        grid_h.addWidget(self.solid_cb, 0, 1)
+
+        # Multicolored CB
+        self.multicolored_cb = FCCheckBox(label=_('Multi-Color'))
+        self.multicolored_cb.setToolTip(
+            _("Draw polygons in different colors.")
+        )
+        self.multicolored_cb.setMinimumWidth(55)
+        grid_h.addWidget(self.multicolored_cb, 0, 2)
 
 
         # ## Object name
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.name_hlay = QtWidgets.QHBoxLayout()
-        self.custom_box.addLayout(self.name_hlay)
+        grid_h.addLayout(self.name_hlay, 1, 0, 1, 3)
+
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
@@ -1546,12 +1298,28 @@ class GeometryObjectUI(ObjectUI):
         )
         )
 
 
         # Plot options
         # Plot options
+        grid_header = QtWidgets.QGridLayout()
+        grid_header.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.custom_box.addLayout(grid_header)
+        grid_header.setColumnStretch(0, 0)
+        grid_header.setColumnStretch(1, 1)
+
         self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
         self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
-        self.custom_box.addWidget(self.plot_options_label)
+        self.plot_options_label.setMinimumWidth(90)
+
+        grid_header.addWidget(self.plot_options_label, 0, 0)
+
+        # Multicolored CB
+        self.multicolored_cb = FCCheckBox(label=_('Multi-Color'))
+        self.multicolored_cb.setToolTip(
+            _("Draw polygons in different colors.")
+        )
+        self.multicolored_cb.setMinimumWidth(55)
+        grid_header.addWidget(self.multicolored_cb, 0, 2)
 
 
         # ## Object name
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.name_hlay = QtWidgets.QHBoxLayout()
-        self.custom_box.addLayout(self.name_hlay)
+        grid_header.addLayout(self.name_hlay, 1, 0, 1, 3)
 
 
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry = FCEntry()
@@ -1715,19 +1483,24 @@ class GeometryObjectUI(ObjectUI):
         grid1.addWidget(self.addtool_entry_lbl, 3, 0)
         grid1.addWidget(self.addtool_entry_lbl, 3, 0)
         grid1.addWidget(self.addtool_entry, 3, 1)
         grid1.addWidget(self.addtool_entry, 3, 1)
 
 
+        bhlay = QtWidgets.QHBoxLayout()
+
         self.addtool_btn = QtWidgets.QPushButton(_('Add'))
         self.addtool_btn = QtWidgets.QPushButton(_('Add'))
         self.addtool_btn.setToolTip(
         self.addtool_btn.setToolTip(
             _("Add a new tool to the Tool Table\n"
             _("Add a new tool to the Tool Table\n"
-              "with the specified diameter.")
+              "with the diameter specified above.")
         )
         )
-        grid1.addWidget(self.addtool_btn, 4, 0, 1, 2)
 
 
         self.addtool_from_db_btn = QtWidgets.QPushButton(_('Add from DB'))
         self.addtool_from_db_btn = QtWidgets.QPushButton(_('Add from DB'))
         self.addtool_from_db_btn.setToolTip(
         self.addtool_from_db_btn.setToolTip(
             _("Add a new tool to the Tool Table\n"
             _("Add a new tool to the Tool Table\n"
               "from the Tool DataBase.")
               "from the Tool DataBase.")
         )
         )
-        grid1.addWidget(self.addtool_from_db_btn, 7, 0, 1, 2)
+
+        bhlay.addWidget(self.addtool_btn)
+        bhlay.addWidget(self.addtool_from_db_btn)
+
+        grid1.addLayout(bhlay, 5, 0, 1, 2)
 
 
         separator_line = QtWidgets.QFrame()
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)

+ 82 - 17
AppGUI/PlotCanvas.py

@@ -12,9 +12,17 @@ from AppGUI.VisPyCanvas import VisPyCanvas, Color
 from AppGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
 from AppGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
 from vispy.scene.visuals import InfiniteLine, Line, Rectangle, Text
 from vispy.scene.visuals import InfiniteLine, Line, Rectangle, Text
 
 
+import gettext
+import AppTranslation as fcTranslate
+import builtins
+
 import numpy as np
 import numpy as np
 from vispy.geometry import Rect
 from vispy.geometry import Rect
 
 
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
 log = logging.getLogger('base')
 log = logging.getLogger('base')
 
 
 
 
@@ -133,11 +141,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
         self.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 0.8), vertical=False,
         self.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 0.8), vertical=False,
                                    parent=self.view.scene)
                                    parent=self.view.scene)
 
 
-        # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
-        # all CNC have a limited workspace
-        if self.fcapp.defaults['global_workspace'] is True:
-            self.draw_workspace(workspace_size=self.fcapp.defaults["global_workspaceT"])
-
         self.line_parent = None
         self.line_parent = None
         if self.fcapp.defaults["global_cursor_color_enabled"]:
         if self.fcapp.defaults["global_cursor_color_enabled"]:
             c_color = Color(self.fcapp.defaults["global_cursor_color"]).rgba
             c_color = Color(self.fcapp.defaults["global_cursor_color"]).rgba
@@ -150,9 +153,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
         self.cursor_h_line = InfiniteLine(pos=None, color=c_color, vertical=False,
         self.cursor_h_line = InfiniteLine(pos=None, color=c_color, vertical=False,
                                           parent=self.line_parent)
                                           parent=self.line_parent)
 
 
-        # HUD Display
-        self.hud_enabled = False
-
         # font size
         # font size
         qsettings = QtCore.QSettings("Open Source", "FlatCAM")
         qsettings = QtCore.QSettings("Open Source", "FlatCAM")
         if qsettings.contains("hud_font_size"):
         if qsettings.contains("hud_font_size"):
@@ -181,10 +181,27 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
                                   border_color=self.rect_hud_color, color=self.rect_hud_color, parent=None)
                                   border_color=self.rect_hud_color, color=self.rect_hud_color, parent=None)
         self.rect_hud.set_gl_state(depth_test=False)
         self.rect_hud.set_gl_state(depth_test=False)
 
 
+        # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
+        # all CNC have a limited workspace
+        if self.fcapp.defaults['global_workspace'] is True:
+            self.draw_workspace(workspace_size=self.fcapp.defaults["global_workspaceT"])
+
+        # HUD Display
+        self.hud_enabled = False
+
         # enable the HUD if it is activated in FlatCAM Preferences
         # enable the HUD if it is activated in FlatCAM Preferences
         if self.fcapp.defaults['global_hud'] is True:
         if self.fcapp.defaults['global_hud'] is True:
             self.on_toggle_hud(state=True)
             self.on_toggle_hud(state=True)
 
 
+        # Axis Display
+        self.axis_enabled = True
+
+        # enable Axis
+        self.on_toggle_axis(state=True)
+
+        # enable Grid lines
+        self.grid_lines_enabled = True
+
         self.shape_collections = []
         self.shape_collections = []
 
 
         self.shape_collection = self.new_shape_collection()
         self.shape_collection = self.new_shape_collection()
@@ -201,27 +218,75 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
 
 
         self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
         self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
 
 
-    def on_toggle_hud(self, state):
+    def on_toggle_axis(self, signal=None, state=None):
+        if state is None:
+            state = not self.axis_enabled
+
+        if state:
+            self.axis_enabled = True
+            self.v_line.parent = self.view.scene
+            self.h_line.parent = self.view.scene
+            self.fcapp.ui.axis_status_label.setStyleSheet("""
+                                                          QLabel
+                                                          {
+                                                              color: black;
+                                                              background-color: peachpuff;
+                                                          }
+                                                          """)
+            self.fcapp.inform[str, bool].emit(_("Axis enabled."), False)
+        else:
+            self.axis_enabled = False
+            self.v_line.parent = None
+            self.h_line.parent = None
+            self.fcapp.ui.axis_status_label.setStyleSheet("")
+            self.fcapp.inform[str, bool].emit(_("Axis disabled."), False)
+
+    def on_toggle_hud(self, signal=None, state=None):
+        if state is None:
+            state = not self.hud_enabled
+
         if state:
         if state:
             self.hud_enabled = True
             self.hud_enabled = True
             self.rect_hud.parent = self.view
             self.rect_hud.parent = self.view
             self.text_hud.parent = self.view
             self.text_hud.parent = self.view
-
             self.fcapp.defaults['global_hud'] = True
             self.fcapp.defaults['global_hud'] = True
             self.fcapp.ui.hud_label.setStyleSheet("""
             self.fcapp.ui.hud_label.setStyleSheet("""
-                            QLabel
-                            {
-                                color: black;
-                                background-color: lightblue;
-                            }
-                            """)
+                                                  QLabel
+                                                  {
+                                                      color: black;
+                                                      background-color: lightblue;
+                                                  }
+                                                  """)
+            self.fcapp.inform[str, bool].emit(_("HUD enabled."), False)
+
         else:
         else:
             self.hud_enabled = False
             self.hud_enabled = False
             self.rect_hud.parent = None
             self.rect_hud.parent = None
             self.text_hud.parent = None
             self.text_hud.parent = None
-
             self.fcapp.defaults['global_hud'] = False
             self.fcapp.defaults['global_hud'] = False
             self.fcapp.ui.hud_label.setStyleSheet("")
             self.fcapp.ui.hud_label.setStyleSheet("")
+            self.fcapp.inform[str, bool].emit(_("HUD disabled."), False)
+
+    def on_toggle_grid_lines(self):
+        state = not self.grid_lines_enabled
+
+        if state:
+            self.grid_lines_enabled = True
+            self.grid.parent = self.view.scene
+            self.fcapp.inform[str, bool].emit(_("Grid enabled."), False)
+        else:
+            self.grid_lines_enabled = False
+            self.grid.parent = None
+            self.fcapp.inform[str, bool].emit(_("Grid disabled."), False)
+
+        # HACK: enabling/disabling the cursor seams to somehow update the shapes on screen
+        # - perhaps is a bug in VisPy implementation
+        if self.fcapp.grid_status():
+            self.fcapp.app_cursor.enabled = False
+            self.fcapp.app_cursor.enabled = True
+        else:
+            self.fcapp.app_cursor.enabled = True
+            self.fcapp.app_cursor.enabled = False
 
 
     def draw_workspace(self, workspace_size):
     def draw_workspace(self, workspace_size):
         """
         """

+ 71 - 12
AppGUI/PlotCanvasLegacy.py

@@ -38,7 +38,6 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
     _ = gettext.gettext
 
 
-
 log = logging.getLogger('base')
 log = logging.getLogger('base')
 
 
 
 
@@ -310,6 +309,9 @@ class PlotCanvasLegacy(QtCore.QObject):
         self.hud_enabled = False
         self.hud_enabled = False
         self.text_hud = self.Thud(plotcanvas=self)
         self.text_hud = self.Thud(plotcanvas=self)
 
 
+        # enable Grid lines
+        self.grid_lines_enabled = True
+
         # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
         # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
         # all CNC have a limited workspace
         # all CNC have a limited workspace
         if self.app.defaults['global_workspace'] is True:
         if self.app.defaults['global_workspace'] is True:
@@ -318,25 +320,62 @@ class PlotCanvasLegacy(QtCore.QObject):
         if self.app.defaults['global_hud'] is True:
         if self.app.defaults['global_hud'] is True:
             self.on_toggle_hud(state=True)
             self.on_toggle_hud(state=True)
 
 
-    def on_toggle_hud(self, state):
+        # Axis Display
+        self.axis_enabled = True
+
+        # enable Axis
+        self.on_toggle_axis(state=True)
+
+    def on_toggle_axis(self, signal=None, state=None):
+        if state is None:
+            state = not self.axis_enabled
+
+        if state:
+            self.axis_enabled = True
+            if self.h_line not in self.axes.lines and self.v_line not in self.axes.lines:
+                self.h_line = self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
+                self.v_line = self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
+                self.app.ui.axis_status_label.setStyleSheet("""
+                                                            QLabel
+                                                            {
+                                                                color: black;
+                                                                background-color: peachpuff;
+                                                            }
+                                                            """)
+                self.app.inform[str, bool].emit(_("Axis enabled."), False)
+        else:
+            self.axis_enabled = False
+            if self.h_line in self.axes.lines and self.v_line in self.axes.lines:
+                self.axes.lines.remove(self.h_line)
+                self.axes.lines.remove(self.v_line)
+                self.app.ui.axis_status_label.setStyleSheet("")
+                self.app.inform[str, bool].emit(_("Axis disabled."), False)
+
+        self.canvas.draw()
+
+    def on_toggle_hud(self, signal=None, state=None):
+        if state is None:
+            state = not self.hud_enabled
+
         if state:
         if state:
             self.hud_enabled = True
             self.hud_enabled = True
             self.text_hud.add_artist()
             self.text_hud.add_artist()
-
             self.app.defaults['global_hud'] = True
             self.app.defaults['global_hud'] = True
-            self.fcapp.ui.hud_label.setStyleSheet("""
-                            QLabel
-                            {
-                                color: black;
-                                background-color: lightblue;
-                            }
-                            """)
+
+            self.app.ui.hud_label.setStyleSheet("""
+                                                QLabel
+                                                {
+                                                    color: black;
+                                                    background-color: lightblue;
+                                                }
+                                                """)
+            self.app.inform[str, bool].emit(_("HUD enabled."), False)
         else:
         else:
             self.hud_enabled = False
             self.hud_enabled = False
             self.text_hud.remove_artist()
             self.text_hud.remove_artist()
-
             self.app.defaults['global_hud'] = False
             self.app.defaults['global_hud'] = False
-            self.fcapp.ui.hud_label.setStyleSheet("")
+            self.app.ui.hud_label.setStyleSheet("")
+            self.app.inform[str, bool].emit(_("HUD disabled."), False)
 
 
         self.canvas.draw()
         self.canvas.draw()
 
 
@@ -399,6 +438,26 @@ class PlotCanvasLegacy(QtCore.QObject):
             if self.hud_holder in self.p.axes.artists:
             if self.hud_holder in self.p.axes.artists:
                 self.p.axes.artists.remove(self.hud_holder)
                 self.p.axes.artists.remove(self.hud_holder)
 
 
+    def on_toggle_grid_lines(self):
+        state = not self.grid_lines_enabled
+
+        if state:
+            self.grid_lines_enabled = True
+            self.axes.grid(True)
+            try:
+                self.canvas.draw()
+            except IndexError:
+                pass
+            self.app.inform[str, bool].emit(_("Grid enabled."), False)
+        else:
+            self.grid_lines_enabled = False
+            self.axes.grid(False)
+            try:
+                self.canvas.draw()
+            except IndexError:
+                pass
+            self.app.inform[str, bool].emit(_("Grid disabled."), False)
+
     def draw_workspace(self, workspace_size):
     def draw_workspace(self, workspace_size):
         """
         """
         Draw a rectangular shape on canvas to specify our valid workspace.
         Draw a rectangular shape on canvas to specify our valid workspace.

+ 120 - 106
AppGUI/preferences/PreferencesUIManager.py

@@ -127,12 +127,6 @@ class PreferencesUIManager:
             "gerber_plot_line": self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry,
             "gerber_plot_line": self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry,
 
 
             # Gerber Options
             # Gerber Options
-            "gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry,
-            "gerber_isopasses": self.ui.gerber_defaults_form.gerber_opt_group.iso_width_entry,
-            "gerber_isooverlap": self.ui.gerber_defaults_form.gerber_opt_group.iso_overlap_entry,
-            "gerber_combine_passes": self.ui.gerber_defaults_form.gerber_opt_group.combine_passes_cb,
-            "gerber_iso_scope": self.ui.gerber_defaults_form.gerber_opt_group.select_combo,
-            "gerber_milling_type": self.ui.gerber_defaults_form.gerber_opt_group.milling_type_radio,
             "gerber_noncoppermargin": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_margin_entry,
             "gerber_noncoppermargin": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_margin_entry,
             "gerber_noncopperrounded": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_rounded_cb,
             "gerber_noncopperrounded": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_rounded_cb,
             "gerber_bboxmargin": self.ui.gerber_defaults_form.gerber_opt_group.bbmargin_entry,
             "gerber_bboxmargin": self.ui.gerber_defaults_form.gerber_opt_group.bbmargin_entry,
@@ -143,12 +137,6 @@ class PreferencesUIManager:
             # "gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry,
             # "gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry,
             # "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry,
             # "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry,
             "gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb,
             "gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb,
-            "gerber_tool_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.tool_type_radio,
-            "gerber_vtipdia": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipdia_spinner,
-            "gerber_vtipangle": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipangle_spinner,
-            "gerber_vcutz": self.ui.gerber_defaults_form.gerber_adv_opt_group.cutz_spinner,
-            "gerber_iso_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.iso_type_radio,
-
             "gerber_buffering": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffering_radio,
             "gerber_buffering": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffering_radio,
             "gerber_simplification": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplify_cb,
             "gerber_simplification": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplify_cb,
             "gerber_simp_tolerance": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplification_tol_spinner,
             "gerber_simp_tolerance": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplification_tol_spinner,
@@ -180,6 +168,7 @@ class PreferencesUIManager:
             # Excellon General
             # Excellon General
             "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
             "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
             "excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb,
             "excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb,
+            "excellon_multicolored": self.ui.excellon_defaults_form.excellon_gen_group.multicolored_cb,
             "excellon_format_upper_in":
             "excellon_format_upper_in":
                 self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry,
                 self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry,
             "excellon_format_lower_in":
             "excellon_format_lower_in":
@@ -221,31 +210,31 @@ class PreferencesUIManager:
             "excellon_gcode_type": self.ui.excellon_defaults_form.excellon_opt_group.excellon_gcode_type_radio,
             "excellon_gcode_type": self.ui.excellon_defaults_form.excellon_opt_group.excellon_gcode_type_radio,
 
 
             # Excellon Advanced Options
             # Excellon Advanced Options
-            "excellon_offset": self.ui.excellon_defaults_form.excellon_adv_opt_group.offset_entry,
-            "excellon_toolchangexy": self.ui.excellon_defaults_form.excellon_adv_opt_group.toolchangexy_entry,
-            "excellon_startz": self.ui.excellon_defaults_form.excellon_adv_opt_group.estartz_entry,
-            "excellon_feedrate_rapid": self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_rapid_entry,
-            "excellon_z_pdepth": self.ui.excellon_defaults_form.excellon_adv_opt_group.pdepth_entry,
-            "excellon_feedrate_probe": self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_probe_entry,
-            "excellon_spindledir": self.ui.excellon_defaults_form.excellon_adv_opt_group.spindledir_radio,
-            "excellon_f_plunge": self.ui.excellon_defaults_form.excellon_adv_opt_group.fplunge_cb,
-            "excellon_f_retract": self.ui.excellon_defaults_form.excellon_adv_opt_group.fretract_cb,
+            "excellon_offset":          self.ui.excellon_defaults_form.excellon_adv_opt_group.offset_entry,
+            "excellon_toolchangexy":    self.ui.excellon_defaults_form.excellon_adv_opt_group.toolchangexy_entry,
+            "excellon_startz":          self.ui.excellon_defaults_form.excellon_adv_opt_group.estartz_entry,
+            "excellon_feedrate_rapid":  self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_rapid_entry,
+            "excellon_z_pdepth":        self.ui.excellon_defaults_form.excellon_adv_opt_group.pdepth_entry,
+            "excellon_feedrate_probe":  self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_probe_entry,
+            "excellon_spindledir":      self.ui.excellon_defaults_form.excellon_adv_opt_group.spindledir_radio,
+            "excellon_f_plunge":        self.ui.excellon_defaults_form.excellon_adv_opt_group.fplunge_cb,
+            "excellon_f_retract":       self.ui.excellon_defaults_form.excellon_adv_opt_group.fretract_cb,
 
 
             # Excellon Export
             # Excellon Export
-            "excellon_exp_units": self.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio,
-            "excellon_exp_format": self.ui.excellon_defaults_form.excellon_exp_group.format_radio,
-            "excellon_exp_integer": self.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry,
-            "excellon_exp_decimals": self.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry,
-            "excellon_exp_zeros": self.ui.excellon_defaults_form.excellon_exp_group.zeros_radio,
-            "excellon_exp_slot_type": self.ui.excellon_defaults_form.excellon_exp_group.slot_type_radio,
+            "excellon_exp_units":       self.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio,
+            "excellon_exp_format":      self.ui.excellon_defaults_form.excellon_exp_group.format_radio,
+            "excellon_exp_integer":     self.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry,
+            "excellon_exp_decimals":    self.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry,
+            "excellon_exp_zeros":       self.ui.excellon_defaults_form.excellon_exp_group.zeros_radio,
+            "excellon_exp_slot_type":   self.ui.excellon_defaults_form.excellon_exp_group.slot_type_radio,
 
 
             # Excellon Editor
             # Excellon Editor
-            "excellon_editor_sel_limit": self.ui.excellon_defaults_form.excellon_editor_group.sel_limit_entry,
-            "excellon_editor_newdia": self.ui.excellon_defaults_form.excellon_editor_group.addtool_entry,
-            "excellon_editor_array_size": self.ui.excellon_defaults_form.excellon_editor_group.drill_array_size_entry,
-            "excellon_editor_lin_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_axis_radio,
-            "excellon_editor_lin_pitch": self.ui.excellon_defaults_form.excellon_editor_group.drill_pitch_entry,
-            "excellon_editor_lin_angle": self.ui.excellon_defaults_form.excellon_editor_group.drill_angle_entry,
+            "excellon_editor_sel_limit":    self.ui.excellon_defaults_form.excellon_editor_group.sel_limit_entry,
+            "excellon_editor_newdia":       self.ui.excellon_defaults_form.excellon_editor_group.addtool_entry,
+            "excellon_editor_array_size":   self.ui.excellon_defaults_form.excellon_editor_group.drill_array_size_entry,
+            "excellon_editor_lin_dir":      self.ui.excellon_defaults_form.excellon_editor_group.drill_axis_radio,
+            "excellon_editor_lin_pitch":    self.ui.excellon_defaults_form.excellon_editor_group.drill_pitch_entry,
+            "excellon_editor_lin_angle":    self.ui.excellon_defaults_form.excellon_editor_group.drill_angle_entry,
             "excellon_editor_circ_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_dir_radio,
             "excellon_editor_circ_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_dir_radio,
             "excellon_editor_circ_angle":
             "excellon_editor_circ_angle":
                 self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_angle_entry,
                 self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_angle_entry,
@@ -270,94 +259,117 @@ class PreferencesUIManager:
                 self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_angle_entry,
                 self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_angle_entry,
 
 
             # Geometry General
             # Geometry General
-            "geometry_plot": self.ui.geometry_defaults_form.geometry_gen_group.plot_cb,
-            "geometry_circle_steps": self.ui.geometry_defaults_form.geometry_gen_group.circle_steps_entry,
-            "geometry_cnctooldia": self.ui.geometry_defaults_form.geometry_gen_group.cnctooldia_entry,
-            "geometry_plot_line": self.ui.geometry_defaults_form.geometry_gen_group.line_color_entry,
+            "geometry_plot":            self.ui.geometry_defaults_form.geometry_gen_group.plot_cb,
+            "geometry_multicolored":    self.ui.geometry_defaults_form.geometry_gen_group.multicolored_cb,
+            "geometry_circle_steps":    self.ui.geometry_defaults_form.geometry_gen_group.circle_steps_entry,
+            "geometry_cnctooldia":      self.ui.geometry_defaults_form.geometry_gen_group.cnctooldia_entry,
+            "geometry_plot_line":       self.ui.geometry_defaults_form.geometry_gen_group.line_color_entry,
 
 
             # Geometry Options
             # Geometry Options
-            "geometry_cutz": self.ui.geometry_defaults_form.geometry_opt_group.cutz_entry,
-            "geometry_travelz": self.ui.geometry_defaults_form.geometry_opt_group.travelz_entry,
-            "geometry_feedrate": self.ui.geometry_defaults_form.geometry_opt_group.cncfeedrate_entry,
-            "geometry_feedrate_z": self.ui.geometry_defaults_form.geometry_opt_group.feedrate_z_entry,
-            "geometry_spindlespeed": self.ui.geometry_defaults_form.geometry_opt_group.cncspindlespeed_entry,
-            "geometry_dwell": self.ui.geometry_defaults_form.geometry_opt_group.dwell_cb,
-            "geometry_dwelltime": self.ui.geometry_defaults_form.geometry_opt_group.dwelltime_entry,
-            "geometry_ppname_g": self.ui.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb,
-            "geometry_toolchange": self.ui.geometry_defaults_form.geometry_opt_group.toolchange_cb,
-            "geometry_toolchangez": self.ui.geometry_defaults_form.geometry_opt_group.toolchangez_entry,
-            "geometry_endz": self.ui.geometry_defaults_form.geometry_opt_group.endz_entry,
-            "geometry_endxy": self.ui.geometry_defaults_form.geometry_opt_group.endxy_entry,
-            "geometry_depthperpass": self.ui.geometry_defaults_form.geometry_opt_group.depthperpass_entry,
-            "geometry_multidepth": self.ui.geometry_defaults_form.geometry_opt_group.multidepth_cb,
+            "geometry_cutz":            self.ui.geometry_defaults_form.geometry_opt_group.cutz_entry,
+            "geometry_travelz":         self.ui.geometry_defaults_form.geometry_opt_group.travelz_entry,
+            "geometry_feedrate":        self.ui.geometry_defaults_form.geometry_opt_group.cncfeedrate_entry,
+            "geometry_feedrate_z":      self.ui.geometry_defaults_form.geometry_opt_group.feedrate_z_entry,
+            "geometry_spindlespeed":    self.ui.geometry_defaults_form.geometry_opt_group.cncspindlespeed_entry,
+            "geometry_dwell":           self.ui.geometry_defaults_form.geometry_opt_group.dwell_cb,
+            "geometry_dwelltime":       self.ui.geometry_defaults_form.geometry_opt_group.dwelltime_entry,
+            "geometry_ppname_g":        self.ui.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb,
+            "geometry_toolchange":      self.ui.geometry_defaults_form.geometry_opt_group.toolchange_cb,
+            "geometry_toolchangez":     self.ui.geometry_defaults_form.geometry_opt_group.toolchangez_entry,
+            "geometry_endz":            self.ui.geometry_defaults_form.geometry_opt_group.endz_entry,
+            "geometry_endxy":           self.ui.geometry_defaults_form.geometry_opt_group.endxy_entry,
+            "geometry_depthperpass":    self.ui.geometry_defaults_form.geometry_opt_group.depthperpass_entry,
+            "geometry_multidepth":      self.ui.geometry_defaults_form.geometry_opt_group.multidepth_cb,
 
 
             # Geometry Advanced Options
             # Geometry Advanced Options
-            "geometry_toolchangexy": self.ui.geometry_defaults_form.geometry_adv_opt_group.toolchangexy_entry,
-            "geometry_startz": self.ui.geometry_defaults_form.geometry_adv_opt_group.gstartz_entry,
-            "geometry_feedrate_rapid": self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_rapid_entry,
-            "geometry_extracut": self.ui.geometry_defaults_form.geometry_adv_opt_group.extracut_cb,
+            "geometry_toolchangexy":    self.ui.geometry_defaults_form.geometry_adv_opt_group.toolchangexy_entry,
+            "geometry_startz":          self.ui.geometry_defaults_form.geometry_adv_opt_group.gstartz_entry,
+            "geometry_feedrate_rapid":  self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_rapid_entry,
+            "geometry_extracut":        self.ui.geometry_defaults_form.geometry_adv_opt_group.extracut_cb,
             "geometry_extracut_length": self.ui.geometry_defaults_form.geometry_adv_opt_group.e_cut_entry,
             "geometry_extracut_length": self.ui.geometry_defaults_form.geometry_adv_opt_group.e_cut_entry,
-            "geometry_z_pdepth": self.ui.geometry_defaults_form.geometry_adv_opt_group.pdepth_entry,
-            "geometry_feedrate_probe": self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_probe_entry,
-            "geometry_spindledir": self.ui.geometry_defaults_form.geometry_adv_opt_group.spindledir_radio,
-            "geometry_f_plunge": self.ui.geometry_defaults_form.geometry_adv_opt_group.fplunge_cb,
-            "geometry_segx": self.ui.geometry_defaults_form.geometry_adv_opt_group.segx_entry,
-            "geometry_segy": self.ui.geometry_defaults_form.geometry_adv_opt_group.segy_entry,
-            "geometry_area_exclusion": self.ui.geometry_defaults_form.geometry_adv_opt_group.exclusion_cb,
-            "geometry_area_shape": self.ui.geometry_defaults_form.geometry_adv_opt_group.area_shape_radio,
-            "geometry_area_strategy": self.ui.geometry_defaults_form.geometry_adv_opt_group.strategy_radio,
-            "geometry_area_overz": self.ui.geometry_defaults_form.geometry_adv_opt_group.over_z_entry,
+            "geometry_z_pdepth":        self.ui.geometry_defaults_form.geometry_adv_opt_group.pdepth_entry,
+            "geometry_feedrate_probe":  self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_probe_entry,
+            "geometry_spindledir":      self.ui.geometry_defaults_form.geometry_adv_opt_group.spindledir_radio,
+            "geometry_f_plunge":        self.ui.geometry_defaults_form.geometry_adv_opt_group.fplunge_cb,
+            "geometry_segx":            self.ui.geometry_defaults_form.geometry_adv_opt_group.segx_entry,
+            "geometry_segy":            self.ui.geometry_defaults_form.geometry_adv_opt_group.segy_entry,
+            "geometry_area_exclusion":  self.ui.geometry_defaults_form.geometry_adv_opt_group.exclusion_cb,
+            "geometry_area_shape":      self.ui.geometry_defaults_form.geometry_adv_opt_group.area_shape_radio,
+            "geometry_area_strategy":   self.ui.geometry_defaults_form.geometry_adv_opt_group.strategy_radio,
+            "geometry_area_overz":      self.ui.geometry_defaults_form.geometry_adv_opt_group.over_z_entry,
 
 
             # Geometry Editor
             # Geometry Editor
-            "geometry_editor_sel_limit": self.ui.geometry_defaults_form.geometry_editor_group.sel_limit_entry,
-            "geometry_editor_milling_type": self.ui.geometry_defaults_form.geometry_editor_group.milling_type_radio,
+            "geometry_editor_sel_limit":        self.ui.geometry_defaults_form.geometry_editor_group.sel_limit_entry,
+            "geometry_editor_milling_type":     self.ui.geometry_defaults_form.geometry_editor_group.milling_type_radio,
 
 
             # CNCJob General
             # CNCJob General
-            "cncjob_plot": self.ui.cncjob_defaults_form.cncjob_gen_group.plot_cb,
-            "cncjob_plot_kind": self.ui.cncjob_defaults_form.cncjob_gen_group.cncplot_method_radio,
-            "cncjob_annotation": self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_cb,
-
-            "cncjob_tooldia": self.ui.cncjob_defaults_form.cncjob_gen_group.tooldia_entry,
-            "cncjob_coords_type": self.ui.cncjob_defaults_form.cncjob_gen_group.coords_type_radio,
-            "cncjob_coords_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.coords_dec_entry,
-            "cncjob_fr_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.fr_dec_entry,
-            "cncjob_steps_per_circle": self.ui.cncjob_defaults_form.cncjob_gen_group.steps_per_circle_entry,
-            "cncjob_line_ending": self.ui.cncjob_defaults_form.cncjob_gen_group.line_ending_cb,
-            "cncjob_plot_line": self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_entry,
-            "cncjob_plot_fill": self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_entry,
-            "cncjob_travel_line": self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_entry,
-            "cncjob_travel_fill": self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_entry,
+            "cncjob_plot":              self.ui.cncjob_defaults_form.cncjob_gen_group.plot_cb,
+            "cncjob_plot_kind":         self.ui.cncjob_defaults_form.cncjob_gen_group.cncplot_method_radio,
+            "cncjob_annotation":        self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_cb,
+
+            "cncjob_tooldia":           self.ui.cncjob_defaults_form.cncjob_gen_group.tooldia_entry,
+            "cncjob_coords_type":       self.ui.cncjob_defaults_form.cncjob_gen_group.coords_type_radio,
+            "cncjob_coords_decimals":   self.ui.cncjob_defaults_form.cncjob_gen_group.coords_dec_entry,
+            "cncjob_fr_decimals":       self.ui.cncjob_defaults_form.cncjob_gen_group.fr_dec_entry,
+            "cncjob_steps_per_circle":  self.ui.cncjob_defaults_form.cncjob_gen_group.steps_per_circle_entry,
+            "cncjob_line_ending":       self.ui.cncjob_defaults_form.cncjob_gen_group.line_ending_cb,
+            "cncjob_plot_line":         self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_entry,
+            "cncjob_plot_fill":         self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_entry,
+            "cncjob_travel_line":       self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_entry,
+            "cncjob_travel_fill":       self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_entry,
 
 
             # CNC Job Options
             # CNC Job Options
-            "cncjob_prepend": self.ui.cncjob_defaults_form.cncjob_opt_group.prepend_text,
-            "cncjob_append": self.ui.cncjob_defaults_form.cncjob_opt_group.append_text,
+            "cncjob_prepend":   self.ui.cncjob_defaults_form.cncjob_opt_group.prepend_text,
+            "cncjob_append":    self.ui.cncjob_defaults_form.cncjob_opt_group.append_text,
 
 
             # CNC Job Advanced Options
             # CNC Job Advanced Options
-            "cncjob_toolchange_macro": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_text,
-            "cncjob_toolchange_macro_enable": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_cb,
-            "cncjob_annotation_fontsize": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontsize_sp,
+            "cncjob_toolchange_macro":          self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_text,
+            "cncjob_toolchange_macro_enable":   self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_cb,
+            "cncjob_annotation_fontsize":  self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontsize_sp,
             "cncjob_annotation_fontcolor": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry,
             "cncjob_annotation_fontcolor": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry,
 
 
+            # Isolation Routing Tool
+            "tools_iso_tooldia":        self.ui.tools_defaults_form.tools_iso_group.tool_dia_entry,
+            "tools_iso_order":          self.ui.tools_defaults_form.tools_iso_group.order_radio,
+            "tools_iso_tool_type":      self.ui.tools_defaults_form.tools_iso_group.tool_type_radio,
+            "tools_iso_tool_vtipdia":   self.ui.tools_defaults_form.tools_iso_group.tipdia_entry,
+            "tools_iso_tool_vtipangle": self.ui.tools_defaults_form.tools_iso_group.tipangle_entry,
+            "tools_iso_tool_cutz":      self.ui.tools_defaults_form.tools_iso_group.cutz_entry,
+            "tools_iso_newdia":         self.ui.tools_defaults_form.tools_iso_group.newdia_entry,
+
+            "tools_iso_passes":         self.ui.tools_defaults_form.tools_iso_group.passes_entry,
+            "tools_iso_overlap":        self.ui.tools_defaults_form.tools_iso_group.overlap_entry,
+            "tools_iso_milling_type":   self.ui.tools_defaults_form.tools_iso_group.milling_type_radio,
+            "tools_iso_follow":         self.ui.tools_defaults_form.tools_iso_group.follow_cb,
+            "tools_iso_isotype":        self.ui.tools_defaults_form.tools_iso_group.iso_type_radio,
+
+            "tools_iso_rest":           self.ui.tools_defaults_form.tools_iso_group.rest_cb,
+            "tools_iso_combine_passes": self.ui.tools_defaults_form.tools_iso_group.combine_passes_cb,
+            "tools_iso_isoexcept":      self.ui.tools_defaults_form.tools_iso_group.except_cb,
+            "tools_iso_selection":      self.ui.tools_defaults_form.tools_iso_group.select_combo,
+            "tools_iso_area_shape":     self.ui.tools_defaults_form.tools_iso_group.area_shape_radio,
+            "tools_iso_plotting":       self.ui.tools_defaults_form.tools_iso_group.plotting_radio,
+
             # NCC Tool
             # NCC Tool
-            "tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
-            "tools_nccorder": self.ui.tools_defaults_form.tools_ncc_group.ncc_order_radio,
-            "tools_nccoverlap": self.ui.tools_defaults_form.tools_ncc_group.ncc_overlap_entry,
-            "tools_nccmargin": self.ui.tools_defaults_form.tools_ncc_group.ncc_margin_entry,
-            "tools_nccmethod": self.ui.tools_defaults_form.tools_ncc_group.ncc_method_combo,
-            "tools_nccconnect": self.ui.tools_defaults_form.tools_ncc_group.ncc_connect_cb,
-            "tools_ncccontour": self.ui.tools_defaults_form.tools_ncc_group.ncc_contour_cb,
-            "tools_nccrest": self.ui.tools_defaults_form.tools_ncc_group.ncc_rest_cb,
-            "tools_ncc_offset_choice": self.ui.tools_defaults_form.tools_ncc_group.ncc_choice_offset_cb,
-            "tools_ncc_offset_value": self.ui.tools_defaults_form.tools_ncc_group.ncc_offset_spinner,
-            "tools_nccref": self.ui.tools_defaults_form.tools_ncc_group.select_combo,
-            "tools_ncc_area_shape": self.ui.tools_defaults_form.tools_ncc_group.area_shape_radio,
-            "tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.ncc_plotting_radio,
-            "tools_nccmilling_type": self.ui.tools_defaults_form.tools_ncc_group.milling_type_radio,
-            "tools_ncctool_type": self.ui.tools_defaults_form.tools_ncc_group.tool_type_radio,
-            "tools_ncccutz": self.ui.tools_defaults_form.tools_ncc_group.cutz_entry,
-            "tools_ncctipdia": self.ui.tools_defaults_form.tools_ncc_group.tipdia_entry,
-            "tools_ncctipangle": self.ui.tools_defaults_form.tools_ncc_group.tipangle_entry,
-            "tools_nccnewdia": self.ui.tools_defaults_form.tools_ncc_group.newdia_entry,
+            "tools_ncctools":           self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
+            "tools_nccorder":           self.ui.tools_defaults_form.tools_ncc_group.ncc_order_radio,
+            "tools_nccoverlap":         self.ui.tools_defaults_form.tools_ncc_group.ncc_overlap_entry,
+            "tools_nccmargin":          self.ui.tools_defaults_form.tools_ncc_group.ncc_margin_entry,
+            "tools_nccmethod":          self.ui.tools_defaults_form.tools_ncc_group.ncc_method_combo,
+            "tools_nccconnect":         self.ui.tools_defaults_form.tools_ncc_group.ncc_connect_cb,
+            "tools_ncccontour":         self.ui.tools_defaults_form.tools_ncc_group.ncc_contour_cb,
+            "tools_nccrest":            self.ui.tools_defaults_form.tools_ncc_group.ncc_rest_cb,
+            "tools_ncc_offset_choice":  self.ui.tools_defaults_form.tools_ncc_group.ncc_choice_offset_cb,
+            "tools_ncc_offset_value":   self.ui.tools_defaults_form.tools_ncc_group.ncc_offset_spinner,
+            "tools_nccref":             self.ui.tools_defaults_form.tools_ncc_group.select_combo,
+            "tools_ncc_area_shape":     self.ui.tools_defaults_form.tools_ncc_group.area_shape_radio,
+            "tools_nccmilling_type":    self.ui.tools_defaults_form.tools_ncc_group.milling_type_radio,
+            "tools_ncctool_type":       self.ui.tools_defaults_form.tools_ncc_group.tool_type_radio,
+            "tools_ncccutz":            self.ui.tools_defaults_form.tools_ncc_group.cutz_entry,
+            "tools_ncctipdia":          self.ui.tools_defaults_form.tools_ncc_group.tipdia_entry,
+            "tools_ncctipangle":        self.ui.tools_defaults_form.tools_ncc_group.tipangle_entry,
+            "tools_nccnewdia":          self.ui.tools_defaults_form.tools_ncc_group.newdia_entry,
+            "tools_ncc_plotting":       self.ui.tools_defaults_form.tools_ncc_group.plotting_radio,
 
 
             # CutOut Tool
             # CutOut Tool
             "tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,
             "tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,
@@ -937,6 +949,7 @@ class PreferencesUIManager:
             msgbox.setText(_("Are you sure you want to continue?"))
             msgbox.setText(_("Are you sure you want to continue?"))
             msgbox.setWindowTitle(_("Application restart"))
             msgbox.setWindowTitle(_("Application restart"))
             msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/warning.png'))
             msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/warning.png'))
+            msgbox.setIcon(QtWidgets.QMessageBox.Question)
 
 
             bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
             bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
             msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.NoRole)
             msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.NoRole)
@@ -1111,7 +1124,7 @@ class PreferencesUIManager:
         if self.ui.grb_edit_toolbar.isVisible():
         if self.ui.grb_edit_toolbar.isVisible():
             tb_status += 64
             tb_status += 64
 
 
-        if self.ui.snap_toolbar.isVisible():
+        if self.ui.status_toolbar.isVisible():
             tb_status += 128
             tb_status += 128
 
 
         if self.ui.toolbarshell.isVisible():
         if self.ui.toolbarshell.isVisible():
@@ -1175,6 +1188,7 @@ class PreferencesUIManager:
                              "Do you want to save the Preferences?"))
                              "Do you want to save the Preferences?"))
             msgbox.setWindowTitle(_("Save Preferences"))
             msgbox.setWindowTitle(_("Save Preferences"))
             msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/save_as.png'))
             msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/save_as.png'))
+            msgbox.setIcon(QtWidgets.QMessageBox.Question)
 
 
             bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
             bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
             msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
             msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)

+ 3 - 3
AppGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 from PyQt5.QtCore import QSettings
 
 
-from AppGUI.GUIElements import FCDoubleSpinner, FCEntry, FloatEntry, RadioSet, FCCheckBox
+from AppGUI.GUIElements import FCDoubleSpinner, RadioSet, FCCheckBox, NumericalEvalTupleEntry, NumericalEvalEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
 import gettext
 import AppTranslation as fcTranslate
 import AppTranslation as fcTranslate
@@ -60,7 +60,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
         toolchange_xy_label.setToolTip(
         toolchange_xy_label.setToolTip(
             _("Toolchange X,Y position.")
             _("Toolchange X,Y position.")
         )
         )
-        self.toolchangexy_entry = FCEntry()
+        self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
 
 
         grid1.addWidget(toolchange_xy_label, 1, 0)
         grid1.addWidget(toolchange_xy_label, 1, 0)
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
@@ -71,7 +71,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
             _("Height of the tool just after start.\n"
             _("Height of the tool just after start.\n"
               "Delete the value if you don't need this feature.")
               "Delete the value if you don't need this feature.")
         )
         )
-        self.estartz_entry = FloatEntry()
+        self.estartz_entry = NumericalEvalEntry(border_color='#0069A9')
 
 
         grid1.addWidget(startzlabel, 2, 0)
         grid1.addWidget(startzlabel, 2, 0)
         grid1.addWidget(self.estartz_entry, 2, 1)
         grid1.addWidget(self.estartz_entry, 2, 1)

+ 10 - 1
AppGUI/preferences/excellon/ExcellonGenPrefGroupUI.py

@@ -36,22 +36,31 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
         grid1 = QtWidgets.QGridLayout()
         grid1 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid1)
         self.layout.addLayout(grid1)
 
 
+        # Plot CB
         self.plot_cb = FCCheckBox(label=_('Plot'))
         self.plot_cb = FCCheckBox(label=_('Plot'))
         self.plot_cb.setToolTip(
         self.plot_cb.setToolTip(
             "Plot (show) this object."
             "Plot (show) this object."
         )
         )
         grid1.addWidget(self.plot_cb, 0, 0)
         grid1.addWidget(self.plot_cb, 0, 0)
 
 
+        # Solid CB
         self.solid_cb = FCCheckBox(label=_('Solid'))
         self.solid_cb = FCCheckBox(label=_('Solid'))
         self.solid_cb.setToolTip(
         self.solid_cb.setToolTip(
             "Plot as solid circles."
             "Plot as solid circles."
         )
         )
         grid1.addWidget(self.solid_cb, 0, 1)
         grid1.addWidget(self.solid_cb, 0, 1)
 
 
+        # Multicolored CB
+        self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
+        self.multicolored_cb.setToolTip(
+            _("Draw polygons in different colors.")
+        )
+        grid1.addWidget(self.multicolored_cb, 0, 2)
+
         separator_line = QtWidgets.QFrame()
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid1.addWidget(separator_line, 1, 0, 1, 2)
+        grid1.addWidget(separator_line, 1, 0, 1, 3)
 
 
         grid2 = QtWidgets.QGridLayout()
         grid2 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid2)
         self.layout.addLayout(grid2)

+ 2 - 2
AppGUI/preferences/excellon/ExcellonOptPrefGroupUI.py

@@ -2,7 +2,7 @@ from PyQt5 import QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 from PyQt5.QtCore import Qt, QSettings
 
 
 from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
 from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
-    FCComboBox
+    FCComboBox, NumericalEvalTupleEntry
 from AppGUI.preferences import machinist_setting
 from AppGUI.preferences import machinist_setting
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
 import gettext
@@ -198,7 +198,7 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
               "If no value is entered then there is no move\n"
               "If no value is entered then there is no move\n"
               "on X,Y plane at the end of the job.")
               "on X,Y plane at the end of the job.")
         )
         )
-        self.endxy_entry = FCEntry()
+        self.endxy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
 
 
         grid2.addWidget(endmove_xy_label, 9, 0)
         grid2.addWidget(endmove_xy_label, 9, 0)
         grid2.addWidget(self.endxy_entry, 9, 1)
         grid2.addWidget(self.endxy_entry, 9, 1)

+ 1 - 1
AppGUI/preferences/excellon/ExcellonPreferencesUI.py

@@ -31,7 +31,7 @@ class ExcellonPreferencesUI(QtWidgets.QWidget):
         self.decimals = decimals
         self.decimals = decimals
 
 
         self.excellon_gen_group = ExcellonGenPrefGroupUI(decimals=self.decimals)
         self.excellon_gen_group = ExcellonGenPrefGroupUI(decimals=self.decimals)
-        self.excellon_gen_group.setMinimumWidth(220)
+        self.excellon_gen_group.setMinimumWidth(240)
         self.excellon_opt_group = ExcellonOptPrefGroupUI(decimals=self.decimals)
         self.excellon_opt_group = ExcellonOptPrefGroupUI(decimals=self.decimals)
         self.excellon_opt_group.setMinimumWidth(290)
         self.excellon_opt_group.setMinimumWidth(290)
         self.excellon_exp_group = ExcellonExpPrefGroupUI(decimals=self.decimals)
         self.excellon_exp_group = ExcellonExpPrefGroupUI(decimals=self.decimals)

+ 0 - 8
AppGUI/preferences/general/GeneralAPPSetGroupUI.py

@@ -177,14 +177,6 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
                                               {'label': _('Landscape'), 'value': 'l'},
                                               {'label': _('Landscape'), 'value': 'l'},
                                               ], stretch=False)
                                               ], stretch=False)
 
 
-        self.wks = OptionalInputSection(self.workspace_cb,
-                                        [
-                                            self.workspace_type_lbl,
-                                            self.wk_cb,
-                                            self.wk_orientation_label,
-                                            self.wk_orientation_radio
-                                        ])
-
         grid0.addWidget(self.wk_orientation_label, 8, 0)
         grid0.addWidget(self.wk_orientation_label, 8, 0)
         grid0.addWidget(self.wk_orientation_radio, 8, 1)
         grid0.addWidget(self.wk_orientation_radio, 8, 1)
 
 

+ 18 - 10
AppGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py

@@ -1,7 +1,8 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 from PyQt5.QtCore import QSettings
 
 
-from AppGUI.GUIElements import FCEntry, FloatEntry, FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel
+from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel, NumericalEvalTupleEntry, \
+    NumericalEvalEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 
 import gettext
 import gettext
@@ -46,8 +47,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         toolchange_xy_label.setToolTip(
         toolchange_xy_label.setToolTip(
             _("Toolchange X,Y position.")
             _("Toolchange X,Y position.")
         )
         )
+        self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
+
         grid1.addWidget(toolchange_xy_label, 1, 0)
         grid1.addWidget(toolchange_xy_label, 1, 0)
-        self.toolchangexy_entry = FCEntry()
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
 
 
         # Start move Z
         # Start move Z
@@ -56,8 +58,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
             _("Height of the tool just after starting the work.\n"
             _("Height of the tool just after starting the work.\n"
               "Delete the value if you don't need this feature.")
               "Delete the value if you don't need this feature.")
         )
         )
+        self.gstartz_entry = NumericalEvalEntry(border_color='#0069A9')
+
         grid1.addWidget(startzlabel, 2, 0)
         grid1.addWidget(startzlabel, 2, 0)
-        self.gstartz_entry = FloatEntry()
         grid1.addWidget(self.gstartz_entry, 2, 1)
         grid1.addWidget(self.gstartz_entry, 2, 1)
 
 
         # Feedrate rapids
         # Feedrate rapids
@@ -186,6 +189,11 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(segy_label, 11, 0)
         grid1.addWidget(segy_label, 11, 0)
         grid1.addWidget(self.segy_entry, 11, 1)
         grid1.addWidget(self.segy_entry, 11, 1)
 
 
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid1.addWidget(separator_line, 12, 0, 1, 2)
+
         # -----------------------------
         # -----------------------------
         # --- Area Exclusion ----------
         # --- Area Exclusion ----------
         # -----------------------------
         # -----------------------------
@@ -195,10 +203,10 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
               "Those parameters are available only for\n"
               "Those parameters are available only for\n"
               "Advanced App. Level.")
               "Advanced App. Level.")
         )
         )
-        grid1.addWidget(self.adv_label, 12, 0, 1, 2)
+        grid1.addWidget(self.adv_label, 13, 0, 1, 2)
 
 
         # Exclusion Area CB
         # Exclusion Area CB
-        self.exclusion_cb = FCCheckBox('%s:' % _("Exclusion areas"))
+        self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas"))
         self.exclusion_cb.setToolTip(
         self.exclusion_cb.setToolTip(
             _(
             _(
                 "Include exclusion areas.\n"
                 "Include exclusion areas.\n"
@@ -206,7 +214,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
                 "is forbidden."
                 "is forbidden."
             )
             )
         )
         )
-        grid1.addWidget(self.exclusion_cb, 13, 0, 1, 2)
+        grid1.addWidget(self.exclusion_cb, 14, 0, 1, 2)
 
 
         # Area Selection shape
         # Area Selection shape
         self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
         self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
@@ -217,8 +225,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
         self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
                                           {'label': _("Polygon"), 'value': 'polygon'}])
                                           {'label': _("Polygon"), 'value': 'polygon'}])
 
 
-        grid1.addWidget(self.area_shape_label, 14, 0)
-        grid1.addWidget(self.area_shape_radio, 14, 1)
+        grid1.addWidget(self.area_shape_label, 15, 0)
+        grid1.addWidget(self.area_shape_radio, 15, 1)
 
 
         # Chose Strategy
         # Chose Strategy
         self.strategy_label = FCLabel('%s:' % _("Strategy"))
         self.strategy_label = FCLabel('%s:' % _("Strategy"))
@@ -229,8 +237,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
         self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
                                         {'label': _('Around'), 'value': 'around'}])
                                         {'label': _('Around'), 'value': 'around'}])
 
 
-        grid1.addWidget(self.strategy_label, 15, 0)
-        grid1.addWidget(self.strategy_radio, 15, 1)
+        grid1.addWidget(self.strategy_label, 16, 0)
+        grid1.addWidget(self.strategy_radio, 16, 1)
 
 
         # Over Z
         # Over Z
         self.over_z_label = FCLabel('%s:' % _("Over Z"))
         self.over_z_label = FCLabel('%s:' % _("Over Z"))

+ 11 - 1
AppGUI/preferences/geometry/GeometryGenPrefGroupUI.py

@@ -31,12 +31,22 @@ class GeometryGenPrefGroupUI(OptionsGroupUI):
         self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
         self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
         self.layout.addWidget(self.plot_options_label)
         self.layout.addWidget(self.plot_options_label)
 
 
+        plot_hlay = QtWidgets.QHBoxLayout()
+        self.layout.addLayout((plot_hlay))
+
         # Plot CB
         # Plot CB
         self.plot_cb = FCCheckBox(label=_('Plot'))
         self.plot_cb = FCCheckBox(label=_('Plot'))
         self.plot_cb.setToolTip(
         self.plot_cb.setToolTip(
             _("Plot (show) this object.")
             _("Plot (show) this object.")
         )
         )
-        self.layout.addWidget(self.plot_cb)
+        plot_hlay.addWidget(self.plot_cb)
+
+        # Multicolored CB
+        self.multicolored_cb = FCCheckBox(label=_('M-Color'))
+        self.multicolored_cb.setToolTip(
+            _("Draw polygons in different colors.")
+        )
+        plot_hlay.addWidget(self.multicolored_cb)
 
 
         grid0 = QtWidgets.QGridLayout()
         grid0 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid0)
         self.layout.addLayout(grid0)

+ 3 - 2
AppGUI/preferences/geometry/GeometryOptPrefGroupUI.py

@@ -1,7 +1,8 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 from PyQt5.QtCore import Qt, QSettings
 
 
-from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCEntry, FCSpinner, FCComboBox
+from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCSpinner, FCComboBox, \
+    NumericalEvalTupleEntry
 from AppGUI.preferences import machinist_setting
 from AppGUI.preferences import machinist_setting
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 
@@ -176,7 +177,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
               "If no value is entered then there is no move\n"
               "If no value is entered then there is no move\n"
               "on X,Y plane at the end of the job.")
               "on X,Y plane at the end of the job.")
         )
         )
-        self.endxy_entry = FCEntry()
+        self.endxy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
 
 
         grid1.addWidget(endmove_xy_label, 7, 0)
         grid1.addWidget(endmove_xy_label, 7, 0)
         grid1.addWidget(self.endxy_entry, 7, 1)
         grid1.addWidget(self.endxy_entry, 7, 1)

+ 0 - 79
AppGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py

@@ -63,85 +63,6 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         grid0.addWidget(separator_line, 2, 0, 1, 2)
         grid0.addWidget(separator_line, 2, 0, 1, 2)
 
 
-        # Tool Type
-        self.tool_type_label = QtWidgets.QLabel('<b>%s</b>' % _('Tool Type'))
-        self.tool_type_label.setToolTip(
-            _("Choose which tool to use for Gerber isolation:\n"
-              "'Circular' or 'V-shape'.\n"
-              "When the 'V-shape' is selected then the tool\n"
-              "diameter will depend on the chosen cut depth.")
-        )
-        self.tool_type_radio = RadioSet([{'label': 'Circular', 'value': 'circular'},
-                                         {'label': 'V-Shape', 'value': 'v'}])
-
-        grid0.addWidget(self.tool_type_label, 3, 0)
-        grid0.addWidget(self.tool_type_radio, 3, 1, 1, 2)
-
-        # Tip Dia
-        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
-        self.tipdialabel.setToolTip(
-            _("The tip diameter for V-Shape Tool")
-        )
-        self.tipdia_spinner = FCDoubleSpinner()
-        self.tipdia_spinner.set_precision(self.decimals)
-        self.tipdia_spinner.set_range(-99.9999, 99.9999)
-        self.tipdia_spinner.setSingleStep(0.1)
-        self.tipdia_spinner.setWrapping(True)
-        grid0.addWidget(self.tipdialabel, 4, 0)
-        grid0.addWidget(self.tipdia_spinner, 4, 1, 1, 2)
-
-        # 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_spinner = FCSpinner()
-        self.tipangle_spinner.set_range(1, 180)
-        self.tipangle_spinner.set_step(5)
-        self.tipangle_spinner.setWrapping(True)
-        grid0.addWidget(self.tipanglelabel, 5, 0)
-        grid0.addWidget(self.tipangle_spinner, 5, 1, 1, 2)
-
-        # Cut Z
-        self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
-        self.cutzlabel.setToolTip(
-            _("Cutting depth (negative)\n"
-              "below the copper surface.")
-        )
-        self.cutz_spinner = FCDoubleSpinner()
-        self.cutz_spinner.set_precision(self.decimals)
-        self.cutz_spinner.set_range(-99.9999, 0.0000)
-        self.cutz_spinner.setSingleStep(0.1)
-        self.cutz_spinner.setWrapping(True)
-
-        grid0.addWidget(self.cutzlabel, 6, 0)
-        grid0.addWidget(self.cutz_spinner, 6, 1, 1, 2)
-
-        # 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': _('Exterior'), 'value': 'ext'},
-                                        {'label': _('Interior'), 'value': 'int'}])
-
-        grid0.addWidget(self.iso_type_label, 7, 0,)
-        grid0.addWidget(self.iso_type_radio, 7, 1, 1, 2)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 8, 0, 1, 2)
-
         # Buffering Type
         # Buffering Type
         buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
         buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
         buffering_label.setToolTip(
         buffering_label.setToolTip(

+ 3 - 2
AppGUI/preferences/gerber/GerberEditorPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 from PyQt5.QtCore import QSettings
 
 
-from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet
+from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet, NumericalEvalTupleEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 
 import gettext
 import gettext
@@ -109,8 +109,9 @@ class GerberEditorPrefGroupUI(OptionsGroupUI):
               "The value of the diameter has to use the dot decimals separator.\n"
               "The value of the diameter has to use the dot decimals separator.\n"
               "Valid values: 0.3, 1.0")
               "Valid values: 0.3, 1.0")
         )
         )
+        self.adddim_entry = NumericalEvalTupleEntry(border_color='#0069A9')
+
         grid0.addWidget(self.adddim_label, 5, 0)
         grid0.addWidget(self.adddim_label, 5, 0)
-        self.adddim_entry = FCEntry()
         grid0.addWidget(self.adddim_entry, 5, 1)
         grid0.addWidget(self.adddim_entry, 5, 1)
 
 
         self.grb_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Pad Array'))
         self.grb_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Pad Array'))

+ 9 - 9
AppGUI/preferences/gerber/GerberGenPrefGroupUI.py

@@ -34,26 +34,26 @@ class GerberGenPrefGroupUI(OptionsGroupUI):
         grid0 = QtWidgets.QGridLayout()
         grid0 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid0)
         self.layout.addLayout(grid0)
 
 
+        # Plot CB
+        self.plot_cb = FCCheckBox(label='%s' % _('Plot'))
+        self.plot_options_label.setToolTip(
+            _("Plot (show) this object.")
+        )
+        grid0.addWidget(self.plot_cb, 0, 0)
+
         # Solid CB
         # Solid CB
         self.solid_cb = FCCheckBox(label='%s' % _('Solid'))
         self.solid_cb = FCCheckBox(label='%s' % _('Solid'))
         self.solid_cb.setToolTip(
         self.solid_cb.setToolTip(
             _("Solid color polygons.")
             _("Solid color polygons.")
         )
         )
-        grid0.addWidget(self.solid_cb, 0, 0)
+        grid0.addWidget(self.solid_cb, 0, 1)
 
 
         # Multicolored CB
         # Multicolored CB
         self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
         self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
         self.multicolored_cb.setToolTip(
         self.multicolored_cb.setToolTip(
             _("Draw polygons in different colors.")
             _("Draw polygons in different colors.")
         )
         )
-        grid0.addWidget(self.multicolored_cb, 0, 1)
-
-        # Plot CB
-        self.plot_cb = FCCheckBox(label='%s' % _('Plot'))
-        self.plot_options_label.setToolTip(
-            _("Plot (show) this object.")
-        )
-        grid0.addWidget(self.plot_cb, 0, 2)
+        grid0.addWidget(self.multicolored_cb, 0, 2)
 
 
         # Number of circle steps for circular aperture linear approximation
         # Number of circle steps for circular aperture linear approximation
         self.circle_steps_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))
         self.circle_steps_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))

+ 0 - 90
AppGUI/preferences/gerber/GerberOptPrefGroupUI.py

@@ -28,96 +28,6 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
 
 
         self.setTitle(str(_("Gerber Options")))
         self.setTitle(str(_("Gerber Options")))
 
 
-        # ## Isolation Routing
-        self.isolation_routing_label = QtWidgets.QLabel("<b>%s:</b>" % _("Isolation Routing"))
-        self.isolation_routing_label.setToolTip(
-            _("Create a Geometry object with\n"
-              "toolpaths to cut outside polygons.")
-        )
-        self.layout.addWidget(self.isolation_routing_label)
-
-        # Cutting Tool Diameter
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
-        tdlabel.setToolTip(
-            _("Diameter of the cutting tool.")
-        )
-        grid0.addWidget(tdlabel, 0, 0)
-        self.iso_tool_dia_entry = FCDoubleSpinner()
-        self.iso_tool_dia_entry.set_precision(self.decimals)
-        self.iso_tool_dia_entry.setSingleStep(0.1)
-        self.iso_tool_dia_entry.set_range(-9999, 9999)
-
-        grid0.addWidget(self.iso_tool_dia_entry, 0, 1)
-
-        # Nr of passes
-        passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
-        passlabel.setToolTip(
-            _("Width of the isolation gap in\n"
-              "number (integer) of tool widths.")
-        )
-        self.iso_width_entry = FCSpinner()
-        self.iso_width_entry.set_range(1, 999)
-
-        grid0.addWidget(passlabel, 1, 0)
-        grid0.addWidget(self.iso_width_entry, 1, 1)
-
-        # Pass overlap
-        overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
-        overlabel.setToolTip(
-            _("How much (percentage) of the tool width to overlap each tool pass.")
-        )
-        self.iso_overlap_entry = FCDoubleSpinner(suffix='%')
-        self.iso_overlap_entry.set_precision(self.decimals)
-        self.iso_overlap_entry.setWrapping(True)
-        self.iso_overlap_entry.setRange(0.0000, 99.9999)
-        self.iso_overlap_entry.setSingleStep(0.1)
-
-        grid0.addWidget(overlabel, 2, 0)
-        grid0.addWidget(self.iso_overlap_entry, 2, 1)
-
-        # 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"
-              "- '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"), _("Reference Object")]
-        )
-
-        grid0.addWidget(self.select_label, 3, 0)
-        grid0.addWidget(self.select_combo, 3, 1, 1, 2)
-
-        # Milling Type
-        milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
-        milling_type_label.setToolTip(
-            _("Milling type:\n"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-        grid0.addWidget(milling_type_label, 4, 0)
-        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conventional'), 'value': 'cv'}])
-        grid0.addWidget(self.milling_type_radio, 4, 1)
-
-        # Combine passes
-        self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
-        self.combine_passes_cb.setToolTip(
-            _("Combine all passes into one object")
-        )
-        grid0.addWidget(self.combine_passes_cb, 5, 0, 1, 2)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 6, 0, 1, 2)
-
         # ## Clear non-copper regions
         # ## Clear non-copper regions
         self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
         self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
         self.clearcopper_label.setToolTip(
         self.clearcopper_label.setToolTip(

+ 1 - 0
AppGUI/preferences/gerber/GerberPreferencesUI.py

@@ -44,6 +44,7 @@ class GerberPreferencesUI(QtWidgets.QWidget):
         self.vlay = QtWidgets.QVBoxLayout()
         self.vlay = QtWidgets.QVBoxLayout()
         self.vlay.addWidget(self.gerber_opt_group)
         self.vlay.addWidget(self.gerber_opt_group)
         self.vlay.addWidget(self.gerber_exp_group)
         self.vlay.addWidget(self.gerber_exp_group)
+        self.vlay.addStretch()
 
 
         self.layout.addWidget(self.gerber_gen_group)
         self.layout.addWidget(self.gerber_gen_group)
         self.layout.addLayout(self.vlay)
         self.layout.addLayout(self.vlay)

+ 2 - 2
AppGUI/preferences/tools/Tools2CalPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 from PyQt5.QtCore import QSettings
 
 
-from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry
+from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 
 import gettext
 import gettext
@@ -116,7 +116,7 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
               "(x, y) point will be used,")
               "(x, y) point will be used,")
         )
         )
 
 
-        self.toolchange_xy_entry = FCEntry()
+        self.toolchange_xy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
 
 
         grid_lay.addWidget(toolchangexy_lbl, 7, 0)
         grid_lay.addWidget(toolchangexy_lbl, 7, 0)
         grid_lay.addWidget(self.toolchange_xy_entry, 7, 1, 1, 2)
         grid_lay.addWidget(self.toolchange_xy_entry, 7, 1, 1, 2)

+ 319 - 0
AppGUI/preferences/tools/ToolsISOPrefGroupUI.py

@@ -0,0 +1,319 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import QSettings
+
+from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, NumericalEvalTupleEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+
+import gettext
+import AppTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+settings = QSettings("Open Source", "FlatCAM")
+if settings.contains("machinist"):
+    machinist_setting = settings.value('machinist', type=int)
+else:
+    machinist_setting = 0
+
+
+class ToolsISOPrefGroupUI(OptionsGroupUI):
+    def __init__(self, decimals=4, parent=None):
+        super(ToolsISOPrefGroupUI, self).__init__(self, parent=parent)
+
+        self.setTitle(str(_("Isolation Tool Options")))
+        self.decimals = decimals
+
+        # ## Clear non-copper regions
+        self.iso_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.iso_label.setToolTip(
+            _("Create a Geometry object with\n"
+              "toolpaths to cut around polygons.")
+        )
+        self.layout.addWidget(self.iso_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Tool Dias
+        isotdlabel = QtWidgets.QLabel('<b><font color="green">%s:</font></b>' % _('Tools Dia'))
+        isotdlabel.setToolTip(
+            _("Diameters of the tools, separated by comma.\n"
+              "The value of the diameter has to use the dot decimals separator.\n"
+              "Valid values: 0.3, 1.0")
+        )
+        self.tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
+        self.tool_dia_entry.setPlaceholderText(_("Comma separated values"))
+
+        grid0.addWidget(isotdlabel, 0, 0)
+        grid0.addWidget(self.tool_dia_entry, 0, 1, 1, 2)
+
+        # Tool order Radio Button
+        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.order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
+                                     {'label': _('Forward'), 'value': 'fwd'},
+                                     {'label': _('Reverse'), 'value': 'rev'}])
+
+        grid0.addWidget(self.order_label, 1, 0)
+        grid0.addWidget(self.order_radio, 1, 1, 1, 2)
+
+        # 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")
+        )
+
+        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")
+        )
+
+        grid0.addWidget(self.tool_type_label, 2, 0)
+        grid0.addWidget(self.tool_type_radio, 2, 1, 1, 2)
+
+        # Tip Dia
+        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
+        self.tipdialabel.setToolTip(
+            _("The tip diameter for V-Shape Tool"))
+        self.tipdia_entry = FCDoubleSpinner()
+        self.tipdia_entry.set_precision(self.decimals)
+        self.tipdia_entry.set_range(0, 1000)
+        self.tipdia_entry.setSingleStep(0.1)
+
+        grid0.addWidget(self.tipdialabel, 3, 0)
+        grid0.addWidget(self.tipdia_entry, 3, 1, 1, 2)
+
+        # Tip Angle
+        self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
+        self.tipanglelabel.setToolTip(
+            _("The tip angle for V-Shape Tool.\n"
+              "In degrees."))
+        self.tipangle_entry = FCDoubleSpinner()
+        self.tipangle_entry.set_precision(self.decimals)
+        self.tipangle_entry.set_range(1, 180)
+        self.tipangle_entry.setSingleStep(5)
+        self.tipangle_entry.setWrapping(True)
+
+        grid0.addWidget(self.tipanglelabel, 4, 0)
+        grid0.addWidget(self.tipangle_entry, 4, 1, 1, 2)
+
+        # 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()
+        self.cutz_entry.set_precision(self.decimals)
+        self.cutz_entry.set_range(-9999.9999, 0.0000)
+        self.cutz_entry.setSingleStep(0.1)
+
+        self.cutz_entry.setToolTip(
+           _("Depth of cut into material. Negative value.\n"
+             "In FlatCAM units.")
+        )
+
+        grid0.addWidget(cutzlabel, 5, 0)
+        grid0.addWidget(self.cutz_entry, 5, 1, 1, 2)
+
+        # New Diameter
+        self.newdialabel = QtWidgets.QLabel('%s:' % _('New Dia'))
+        self.newdialabel.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.newdia_entry = FCDoubleSpinner()
+        self.newdia_entry.set_precision(self.decimals)
+        self.newdia_entry.set_range(0.0001, 9999.9999)
+        self.newdia_entry.setSingleStep(0.1)
+
+        grid0.addWidget(self.newdialabel, 6, 0)
+        grid0.addWidget(self.newdia_entry, 6, 1, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 7, 0, 1, 3)
+
+        # Passes
+        passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
+        passlabel.setToolTip(
+            _("Width of the isolation gap in\n"
+              "number (integer) of tool widths.")
+        )
+        self.passes_entry = FCSpinner()
+        self.passes_entry.set_range(1, 999)
+        self.passes_entry.setObjectName("i_passes")
+
+        grid0.addWidget(passlabel, 8, 0)
+        grid0.addWidget(self.passes_entry, 8, 1, 1, 2)
+
+        # Overlap Entry
+        overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
+        overlabel.setToolTip(
+            _("How much (percentage) of the tool width to overlap each tool pass.")
+        )
+        self.overlap_entry = FCDoubleSpinner(suffix='%')
+        self.overlap_entry.set_precision(self.decimals)
+        self.overlap_entry.setWrapping(True)
+        self.overlap_entry.set_range(0.0000, 99.9999)
+        self.overlap_entry.setSingleStep(0.1)
+        self.overlap_entry.setObjectName("i_overlap")
+
+        grid0.addWidget(overlabel, 9, 0)
+        grid0.addWidget(self.overlap_entry, 9, 1, 1, 2)
+
+        # Milling Type Radio Button
+        self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
+        self.milling_type_label.setToolTip(
+            _("Milling type when the selected tool is of type: 'iso_op':\n"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+
+        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
+                                            {'label': _('Conventional'), 'value': 'cv'}])
+        self.milling_type_radio.setToolTip(
+            _("Milling type when the selected tool is of type: 'iso_op':\n"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+
+        grid0.addWidget(self.milling_type_label, 10, 0)
+        grid0.addWidget(self.milling_type_radio, 10, 1, 1, 2)
+
+        # 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.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")
+
+        grid0.addWidget(self.follow_label, 11, 0)
+        grid0.addWidget(self.follow_cb, 11, 1, 1, 2)
+
+        # 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_type")
+
+        grid0.addWidget(self.iso_type_label, 12, 0)
+        grid0.addWidget(self.iso_type_radio, 12, 1, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 13, 0, 1, 3)
+
+        # Rest machining CheckBox
+        self.rest_cb = FCCheckBox('%s' % _("Rest"))
+        self.rest_cb.setObjectName("i_rest_machining")
+        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.")
+        )
+
+        grid0.addWidget(self.rest_cb, 17, 0)
+
+        # 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")
+
+        grid0.addWidget(self.combine_passes_cb, 17, 1)
+
+        # 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")
+        grid0.addWidget(self.except_cb, 17, 2)
+
+        # 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"
+              "- '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")
+
+        grid0.addWidget(self.select_label, 20, 0)
+        grid0.addWidget(self.select_combo, 20, 1, 1, 2)
+
+        # Area Shape
+        self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
+        self.area_shape_label.setToolTip(
+            _("The kind of selection shape used for area selection.")
+        )
+
+        self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
+                                          {'label': _("Polygon"), 'value': 'polygon'}])
+
+        grid0.addWidget(self.area_shape_label, 21, 0)
+        grid0.addWidget(self.area_shape_radio, 21, 1, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 22, 0, 1, 3)
+
+        # ## Plotting type
+        self.plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
+                                        {"label": _("Progressive"), "value": "progressive"}])
+        plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
+        plotting_label.setToolTip(
+            _("- 'Normal' -  normal plotting, done at the end of the job\n"
+              "- 'Progressive' - each shape is plotted after it is generated")
+        )
+        grid0.addWidget(plotting_label, 23, 0)
+        grid0.addWidget(self.plotting_radio, 23, 1, 1, 2)
+
+        self.layout.addStretch()

+ 9 - 9
AppGUI/preferences/tools/ToolsNCCPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 from PyQt5.QtCore import QSettings
 
 
-from AppGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
+from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 
 import gettext
 import gettext
@@ -45,7 +45,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
               "Valid values: 0.3, 1.0")
               "Valid values: 0.3, 1.0")
         )
         )
         grid0.addWidget(ncctdlabel, 0, 0)
         grid0.addWidget(ncctdlabel, 0, 0)
-        self.ncc_tool_dia_entry = FCEntry(border_color='#0069A9')
+        self.ncc_tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
         self.ncc_tool_dia_entry.setPlaceholderText(_("Comma separated values"))
         self.ncc_tool_dia_entry.setPlaceholderText(_("Comma separated values"))
         grid0.addWidget(self.ncc_tool_dia_entry, 0, 1)
         grid0.addWidget(self.ncc_tool_dia_entry, 0, 1)
 
 
@@ -285,7 +285,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(separator_line, 16, 0, 1, 2)
         grid0.addWidget(separator_line, 16, 0, 1, 2)
 
 
         # Rest machining CheckBox
         # Rest machining CheckBox
-        self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining"))
+        self.ncc_rest_cb = FCCheckBox('%s' % _("Rest"))
         self.ncc_rest_cb.setToolTip(
         self.ncc_rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
             _("If checked, use 'rest machining'.\n"
               "Basically it will clear copper outside PCB features,\n"
               "Basically it will clear copper outside PCB features,\n"
@@ -336,14 +336,14 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(separator_line, 20, 0, 1, 2)
         grid0.addWidget(separator_line, 20, 0, 1, 2)
 
 
         # ## Plotting type
         # ## Plotting type
-        self.ncc_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
-                                            {"label": _("Progressive"), "value": "progressive"}])
-        plotting_label = QtWidgets.QLabel('%s:' % _("NCC Plotting"))
+        self.plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
+                                        {"label": _("Progressive"), "value": "progressive"}])
+        plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
         plotting_label.setToolTip(
         plotting_label.setToolTip(
-            _("- 'Normal' -  normal plotting, done at the end of the NCC job\n"
-              "- 'Progressive' - after each shape is generated it will be plotted.")
+            _("- 'Normal' -  normal plotting, done at the end of the job\n"
+              "- 'Progressive' - each shape is plotted after it is generated")
         )
         )
         grid0.addWidget(plotting_label, 21, 0)
         grid0.addWidget(plotting_label, 21, 0)
-        grid0.addWidget(self.ncc_plotting_radio, 21, 1)
+        grid0.addWidget(self.plotting_radio, 21, 1)
 
 
         self.layout.addStretch()
         self.layout.addStretch()

+ 8 - 8
AppGUI/preferences/tools/ToolsPaintPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 from PyQt5.QtCore import QSettings
 
 
-from AppGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
+from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 
 import gettext
 import gettext
@@ -53,7 +53,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         )
         )
         grid0.addWidget(ptdlabel, 0, 0)
         grid0.addWidget(ptdlabel, 0, 0)
 
 
-        self.painttooldia_entry = FCEntry(border_color='#0069A9')
+        self.painttooldia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
         self.painttooldia_entry.setPlaceholderText(_("Comma separated values"))
         self.painttooldia_entry.setPlaceholderText(_("Comma separated values"))
 
 
         grid0.addWidget(self.painttooldia_entry, 0, 1)
         grid0.addWidget(self.painttooldia_entry, 0, 1)
@@ -241,8 +241,8 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         grid0.addWidget(separator_line, 13, 0, 1, 2)
         grid0.addWidget(separator_line, 13, 0, 1, 2)
 
 
-        self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
-        self.rest_cb.setObjectName(_("Rest Machining"))
+        self.rest_cb = FCCheckBox('%s' % _("Rest"))
+        self.rest_cb.setObjectName(_("Rest"))
         self.rest_cb.setToolTip(
         self.rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
             _("If checked, use 'rest machining'.\n"
               "Basically it will clear copper outside PCB features,\n"
               "Basically it will clear copper outside PCB features,\n"
@@ -277,7 +277,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         # )
         # )
         self.selectmethod_combo = FCComboBox()
         self.selectmethod_combo = FCComboBox()
         self.selectmethod_combo.addItems(
         self.selectmethod_combo.addItems(
-            [_("Polygon Selection"), _("Area Selection"), _("All Polygons"), _("Reference Object")]
+            [_("Polygon Selection"), _("Area Selection"), _("All"), _("Reference Object")]
         )
         )
 
 
         grid0.addWidget(selectlabel, 15, 0)
         grid0.addWidget(selectlabel, 15, 0)
@@ -302,10 +302,10 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         # ## Plotting type
         # ## Plotting type
         self.paint_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
         self.paint_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
                                               {"label": _("Progressive"), "value": "progressive"}])
                                               {"label": _("Progressive"), "value": "progressive"}])
-        plotting_label = QtWidgets.QLabel('%s:' % _("Paint Plotting"))
+        plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
         plotting_label.setToolTip(
         plotting_label.setToolTip(
-            _("- 'Normal' -  normal plotting, done at the end of the Paint job\n"
-              "- 'Progressive' - after each shape is generated it will be plotted.")
+            _("- 'Normal' -  normal plotting, done at the end of the job\n"
+              "- 'Progressive' - each shape is plotted after it is generated")
         )
         )
         grid0.addWidget(plotting_label, 20, 0)
         grid0.addWidget(plotting_label, 20, 0)
         grid0.addWidget(self.paint_plotting_radio, 20, 1)
         grid0.addWidget(self.paint_plotting_radio, 20, 1)

+ 8 - 2
AppGUI/preferences/tools/ToolsPreferencesUI.py

@@ -8,10 +8,12 @@ from AppGUI.preferences.tools.ToolsTransformPrefGroupUI import ToolsTransformPre
 from AppGUI.preferences.tools.ToolsCalculatorsPrefGroupUI import ToolsCalculatorsPrefGroupUI
 from AppGUI.preferences.tools.ToolsCalculatorsPrefGroupUI import ToolsCalculatorsPrefGroupUI
 from AppGUI.preferences.tools.ToolsPanelizePrefGroupUI import ToolsPanelizePrefGroupUI
 from AppGUI.preferences.tools.ToolsPanelizePrefGroupUI import ToolsPanelizePrefGroupUI
 from AppGUI.preferences.tools.ToolsFilmPrefGroupUI import ToolsFilmPrefGroupUI
 from AppGUI.preferences.tools.ToolsFilmPrefGroupUI import ToolsFilmPrefGroupUI
-from AppGUI.preferences.tools.ToolsPaintPrefGroupUI import ToolsPaintPrefGroupUI
 from AppGUI.preferences.tools.Tools2sidedPrefGroupUI import Tools2sidedPrefGroupUI
 from AppGUI.preferences.tools.Tools2sidedPrefGroupUI import Tools2sidedPrefGroupUI
+
 from AppGUI.preferences.tools.ToolsCutoutPrefGroupUI import ToolsCutoutPrefGroupUI
 from AppGUI.preferences.tools.ToolsCutoutPrefGroupUI import ToolsCutoutPrefGroupUI
 from AppGUI.preferences.tools.ToolsNCCPrefGroupUI import ToolsNCCPrefGroupUI
 from AppGUI.preferences.tools.ToolsNCCPrefGroupUI import ToolsNCCPrefGroupUI
+from AppGUI.preferences.tools.ToolsPaintPrefGroupUI import ToolsPaintPrefGroupUI
+from AppGUI.preferences.tools.ToolsISOPrefGroupUI import ToolsISOPrefGroupUI
 
 
 import gettext
 import gettext
 import AppTranslation as fcTranslate
 import AppTranslation as fcTranslate
@@ -36,6 +38,9 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
         self.setLayout(self.layout)
         self.setLayout(self.layout)
         self.decimals = decimals
         self.decimals = decimals
 
 
+        self.tools_iso_group = ToolsISOPrefGroupUI(decimals=self.decimals)
+        self.tools_iso_group.setMinimumWidth(220)
+
         self.tools_ncc_group = ToolsNCCPrefGroupUI(decimals=self.decimals)
         self.tools_ncc_group = ToolsNCCPrefGroupUI(decimals=self.decimals)
         self.tools_ncc_group.setMinimumWidth(220)
         self.tools_ncc_group.setMinimumWidth(220)
 
 
@@ -75,7 +80,7 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
 
 
         self.vlay1 = QtWidgets.QVBoxLayout()
         self.vlay1 = QtWidgets.QVBoxLayout()
         self.vlay1.addWidget(self.tools_paint_group)
         self.vlay1.addWidget(self.tools_paint_group)
-        self.vlay1.addWidget(self.tools_panelize_group)
+        self.vlay1.addWidget(self.tools_iso_group)
 
 
         self.vlay2 = QtWidgets.QVBoxLayout()
         self.vlay2 = QtWidgets.QVBoxLayout()
         self.vlay2.addWidget(self.tools_transform_group)
         self.vlay2.addWidget(self.tools_transform_group)
@@ -89,6 +94,7 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
         self.vlay4 = QtWidgets.QVBoxLayout()
         self.vlay4 = QtWidgets.QVBoxLayout()
         self.vlay4.addWidget(self.tools_solderpaste_group)
         self.vlay4.addWidget(self.tools_solderpaste_group)
         self.vlay4.addWidget(self.tools_corners_group)
         self.vlay4.addWidget(self.tools_corners_group)
+        self.vlay4.addWidget(self.tools_panelize_group)
 
 
         self.layout.addLayout(self.vlay)
         self.layout.addLayout(self.vlay)
         self.layout.addLayout(self.vlay1)
         self.layout.addLayout(self.vlay1)

+ 3 - 3
AppGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 from PyQt5.QtCore import QSettings
 
 
-from AppGUI.GUIElements import FCEntry, FCDoubleSpinner, FCSpinner, FCComboBox
+from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, FCComboBox, NumericalEvalTupleEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 
 import gettext
 import gettext
@@ -45,7 +45,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
               "The value of the diameter has to use the dot decimals separator.\n"
               "The value of the diameter has to use the dot decimals separator.\n"
               "Valid values: 0.3, 1.0")
               "Valid values: 0.3, 1.0")
         )
         )
-        self.nozzle_tool_dia_entry = FCEntry()
+        self.nozzle_tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
 
 
         grid0.addWidget(nozzletdlabel, 0, 0)
         grid0.addWidget(nozzletdlabel, 0, 0)
         grid0.addWidget(self.nozzle_tool_dia_entry, 0, 1)
         grid0.addWidget(self.nozzle_tool_dia_entry, 0, 1)
@@ -130,7 +130,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.z_toolchange_entry, 6, 1)
         grid0.addWidget(self.z_toolchange_entry, 6, 1)
 
 
         # X,Y Toolchange location
         # X,Y Toolchange location
-        self.xy_toolchange_entry = FCEntry()
+        self.xy_toolchange_entry = NumericalEvalTupleEntry(border_color='#0069A9')
         self.xy_toolchange_label = QtWidgets.QLabel('%s:' % _("Toolchange X-Y"))
         self.xy_toolchange_label = QtWidgets.QLabel('%s:' % _("Toolchange X-Y"))
         self.xy_toolchange_label.setToolTip(
         self.xy_toolchange_label.setToolTip(
             _("The X,Y location for tool (nozzle) change.\n"
             _("The X,Y location for tool (nozzle) change.\n"

+ 2 - 2
AppGUI/preferences/tools/ToolsTransformPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 from PyQt5.QtCore import QSettings
 
 
-from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCEntry
+from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 
 import gettext
 import gettext
@@ -191,7 +191,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
               "The 'x' in (x, y) will be used when using Flip on X and\n"
               "The 'x' in (x, y) will be used when using Flip on X and\n"
               "the 'y' in (x, y) will be used when using Flip on Y and")
               "the 'y' in (x, y) will be used when using Flip on Y and")
         )
         )
-        self.flip_ref_entry = FCEntry()
+        self.flip_ref_entry = NumericalEvalTupleEntry(border_color='#0069A9')
 
 
         grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2)
         grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2)
         grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2)
         grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2)

+ 2 - 2
AppObjects/FlatCAMCNCJob.py

@@ -506,10 +506,10 @@ class CNCJobObject(FlatCAMObj, CNCjob):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export Machine Code ..."),
                 caption=_("Export Machine Code ..."),
                 directory=dir_file_to_save,
                 directory=dir_file_to_save,
-                filter=_filter_
+                ext_filter=_filter_
             )
             )
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), filter=_filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), ext_filter=_filter_)
 
 
         filename = str(filename)
         filename = str(filename)
 
 

+ 85 - 15
AppObjects/FlatCAMExcellon.py

@@ -19,6 +19,7 @@ from AppParsers.ParseExcellon import Excellon
 from AppObjects.FlatCAMObj import *
 from AppObjects.FlatCAMObj import *
 
 
 import itertools
 import itertools
+import numpy as np
 
 
 import gettext
 import gettext
 import AppTranslation as fcTranslate
 import AppTranslation as fcTranslate
@@ -50,6 +51,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.options.update({
         self.options.update({
             "plot": True,
             "plot": True,
             "solid": False,
             "solid": False,
+            "multicolored": False,
 
 
             "operation": "drill",
             "operation": "drill",
             "milling_type": "drills",
             "milling_type": "drills",
@@ -607,6 +609,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.form_fields.update({
         self.form_fields.update({
             "plot": self.ui.plot_cb,
             "plot": self.ui.plot_cb,
             "solid": self.ui.solid_cb,
             "solid": self.ui.solid_cb,
+            "multicolored": self.ui.multicolored_cb,
 
 
             "operation": self.ui.operation_radio,
             "operation": self.ui.operation_radio,
             "milling_type": self.ui.milling_type_radio,
             "milling_type": self.ui.milling_type_radio,
@@ -708,6 +711,8 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
 
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
         self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
+        self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
+
         self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
         self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
         self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click)
         self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click)
         self.ui.generate_milling_slots_button.clicked.connect(self.on_generate_milling_slots_button_click)
         self.ui.generate_milling_slots_button.clicked.connect(self.on_generate_milling_slots_button_click)
@@ -1180,13 +1185,25 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
 
     def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
     def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
         """
         """
+        Will generate an Geometry Object allowing to cut a drill hole instead of drilling it.
+
         Note: This method is a good template for generic operations as
         Note: This method is a good template for generic operations as
         it takes it's options from parameters or otherwise from the
         it takes it's options from parameters or otherwise from the
         object's options and returns a (success, msg) tuple as feedback
         object's options and returns a (success, msg) tuple as feedback
         for shell operations.
         for shell operations.
 
 
-        :return:    Success/failure condition tuple (bool, str).
-        :rtype:     tuple
+        :param tools:       A list of tools where the drills are to be milled or a string: "all"
+        :type tools:
+        :param outname:     the name of the resulting Geometry object
+        :type outname:      str
+        :param tooldia:     the tool diameter to be used in creation of the milling path (Geometry Object)
+        :type tooldia:      float
+        :param plot:        if to plot the resulting object
+        :type plot:         bool
+        :param use_thread:  if to use threading for creation of the Geometry object
+        :type use_thread:   bool
+        :return:            Success/failure condition tuple (bool, str).
+        :rtype:             tuple
         """
         """
 
 
         # Get the tools from the list. These are keys
         # Get the tools from the list. These are keys
@@ -1250,7 +1267,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options["cnctooldia"] = str(tooldia)
             geo_obj.options["cnctooldia"] = str(tooldia)
-
+            geo_obj.options["multidepth"] = self.options["multidepth"]
             geo_obj.solid_geometry = []
             geo_obj.solid_geometry = []
 
 
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
@@ -1280,15 +1297,27 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
 
         return True, ""
         return True, ""
 
 
-    def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=True, use_thread=False):
+    def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
         """
         """
+        Will generate an Geometry Object allowing to cut/mill a slot hole.
+
         Note: This method is a good template for generic operations as
         Note: This method is a good template for generic operations as
         it takes it's options from parameters or otherwise from the
         it takes it's options from parameters or otherwise from the
         object's options and returns a (success, msg) tuple as feedback
         object's options and returns a (success, msg) tuple as feedback
         for shell operations.
         for shell operations.
 
 
-        :return: Success/failure condition tuple (bool, str).
-        :rtype: tuple
+        :param tools:       A list of tools where the drills are to be milled or a string: "all"
+        :type tools:
+        :param outname:     the name of the resulting Geometry object
+        :type outname:      str
+        :param tooldia:     the tool diameter to be used in creation of the milling path (Geometry Object)
+        :type tooldia:      float
+        :param plot:        if to plot the resulting object
+        :type plot:         bool
+        :param use_thread:  if to use threading for creation of the Geometry object
+        :type use_thread:   bool
+        :return:            Success/failure condition tuple (bool, str).
+        :rtype:             tuple
         """
         """
 
 
         # Get the tools from the list. These are keys
         # Get the tools from the list. These are keys
@@ -1341,7 +1370,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options["cnctooldia"] = str(tooldia)
             geo_obj.options["cnctooldia"] = str(tooldia)
-
+            geo_obj.options["multidepth"] = self.options["multidepth"]
             geo_obj.solid_geometry = []
             geo_obj.solid_geometry = []
 
 
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
@@ -1388,13 +1417,13 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.app.defaults.report_usage("excellon_on_create_milling_drills button")
         self.app.defaults.report_usage("excellon_on_create_milling_drills button")
         self.read_form()
         self.read_form()
 
 
-        self.generate_milling_drills(use_thread=False)
+        self.generate_milling_drills(use_thread=False, plot=True)
 
 
     def on_generate_milling_slots_button_click(self, *args):
     def on_generate_milling_slots_button_click(self, *args):
         self.app.defaults.report_usage("excellon_on_create_milling_slots_button")
         self.app.defaults.report_usage("excellon_on_create_milling_slots_button")
         self.read_form()
         self.read_form()
 
 
-        self.generate_milling_slots(use_thread=False)
+        self.generate_milling_slots(use_thread=False, plot=True)
 
 
     def on_pp_changed(self):
     def on_pp_changed(self):
         current_pp = self.ui.pp_excellon_name_cb.get_value()
         current_pp = self.ui.pp_excellon_name_cb.get_value()
@@ -1727,6 +1756,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.read_form_item('solid')
         self.read_form_item('solid')
         self.plot()
         self.plot()
 
 
+    def on_multicolored_cb_click(self, *args):
+        if self.muted_ui:
+            return
+        self.read_form_item('multicolored')
+        self.plot()
+
     def on_plot_cb_click(self, *args):
     def on_plot_cb_click(self, *args):
         if self.muted_ui:
         if self.muted_ui:
             return
             return
@@ -1800,6 +1835,27 @@ class ExcellonObject(FlatCAMObj, Excellon):
         if not FlatCAMObj.plot(self):
         if not FlatCAMObj.plot(self):
             return
             return
 
 
+        if self.app.is_legacy is False:
+            def random_color():
+                r_color = np.random.rand(4)
+                r_color[3] = 1
+                return r_color
+        else:
+            def random_color():
+                while True:
+                    r_color = np.random.rand(4)
+                    r_color[3] = 1
+
+                    new_color = '#'
+                    for idx in range(len(r_color)):
+                        new_color += '%x' % int(r_color[idx] * 255)
+                    # do it until a valid color is generated
+                    # a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha
+                    # for a total of 9 chars
+                    if len(new_color) == 9:
+                        break
+                return new_color
+
         # try:
         # try:
         #     # Plot Excellon (All polygons?)
         #     # Plot Excellon (All polygons?)
         #     if self.options["solid"]:
         #     if self.options["solid"]:
@@ -1831,12 +1887,26 @@ class ExcellonObject(FlatCAMObj, Excellon):
         try:
         try:
             # Plot Excellon (All polygons?)
             # Plot Excellon (All polygons?)
             if self.options["solid"]:
             if self.options["solid"]:
-                for geo in self.solid_geometry:
-                    self.add_shape(shape=geo,
-                                   color=self.outline_color,
-                                   face_color=self.fill_color,
-                                   visible=visible,
-                                   layer=2)
+                # for geo in self.solid_geometry:
+                #     self.add_shape(shape=geo,
+                #                    color=self.outline_color,
+                #                    face_color=random_color() if self.options['multicolored'] else self.fill_color,
+                #                    visible=visible,
+                #                    layer=2)
+
+                # plot polygons for each tool separately
+                for tool in self.tools:
+                    # set the color here so we have one color for each tool
+                    geo_color = random_color()
+
+                    # tool is a dict also
+                    for geo in self.tools[tool]["solid_geometry"]:
+                        self.add_shape(shape=geo,
+                                       color=geo_color if self.options['multicolored'] else self.outline_color,
+                                       face_color=geo_color if self.options['multicolored'] else self.fill_color,
+                                       visible=visible,
+                                       layer=2)
+
             else:
             else:
                 for geo in self.solid_geometry:
                 for geo in self.solid_geometry:
                     self.add_shape(shape=geo.exterior, color='red', visible=visible)
                     self.add_shape(shape=geo.exterior, color='red', visible=visible)

+ 33 - 1
AppObjects/FlatCAMGeometry.py

@@ -52,6 +52,7 @@ class GeometryObject(FlatCAMObj, Geometry):
 
 
         self.options.update({
         self.options.update({
             "plot": True,
             "plot": True,
+            "multicolored": False,
             "cutz": -0.002,
             "cutz": -0.002,
             "vtipdia": 0.1,
             "vtipdia": 0.1,
             "vtipangle": 30,
             "vtipangle": 30,
@@ -396,6 +397,7 @@ class GeometryObject(FlatCAMObj, Geometry):
 
 
         self.form_fields.update({
         self.form_fields.update({
             "plot": self.ui.plot_cb,
             "plot": self.ui.plot_cb,
+            "multicolored": self.ui.multicolored_cb,
             "cutz": self.ui.cutz_entry,
             "cutz": self.ui.cutz_entry,
             "vtipdia": self.ui.tipdia_entry,
             "vtipdia": self.ui.tipdia_entry,
             "vtipangle": self.ui.tipangle_entry,
             "vtipangle": self.ui.tipangle_entry,
@@ -591,6 +593,8 @@ class GeometryObject(FlatCAMObj, Geometry):
         self.ui.extracut_cb.toggled.connect(lambda state: self.ui.e_cut_entry.setDisabled(not state))
         self.ui.extracut_cb.toggled.connect(lambda state: self.ui.e_cut_entry.setDisabled(not state))
 
 
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
+        self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
+
         self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
         self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
         self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
         self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
         self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
         self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
@@ -2665,6 +2669,27 @@ class GeometryObject(FlatCAMObj, Geometry):
         if not FlatCAMObj.plot(self):
         if not FlatCAMObj.plot(self):
             return
             return
 
 
+        if self.app.is_legacy is False:
+            def random_color():
+                r_color = np.random.rand(4)
+                r_color[3] = 1
+                return r_color
+        else:
+            def random_color():
+                while True:
+                    r_color = np.random.rand(4)
+                    r_color[3] = 1
+
+                    new_color = '#'
+                    for idx in range(len(r_color)):
+                        new_color += '%x' % int(r_color[idx] * 255)
+                    # do it until a valid color is generated
+                    # a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha
+                    # for a total of 9 chars
+                    if len(new_color) == 9:
+                        break
+                return new_color
+
         try:
         try:
             # plot solid geometries found as members of self.tools attribute dict
             # plot solid geometries found as members of self.tools attribute dict
             # for MultiGeo
             # for MultiGeo
@@ -2672,7 +2697,8 @@ class GeometryObject(FlatCAMObj, Geometry):
                 for tooluid_key in self.tools:
                 for tooluid_key in self.tools:
                     solid_geometry = self.tools[tooluid_key]['solid_geometry']
                     solid_geometry = self.tools[tooluid_key]['solid_geometry']
                     self.plot_element(solid_geometry, visible=visible,
                     self.plot_element(solid_geometry, visible=visible,
-                                      color=self.app.defaults["geometry_plot_line"])
+                                      color=random_color() if self.options['multicolored']
+                                      else self.app.defaults["geometry_plot_line"])
             else:
             else:
                 # plot solid geometry that may be an direct attribute of the geometry object
                 # plot solid geometry that may be an direct attribute of the geometry object
                 # for SingleGeo
                 # for SingleGeo
@@ -2740,6 +2766,12 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.ui.plot_cb.setChecked(True)
             self.ui.plot_cb.setChecked(True)
         self.ui_connect()
         self.ui_connect()
 
 
+    def on_multicolored_cb_click(self, *args):
+        if self.muted_ui:
+            return
+        self.read_form_item('multicolored')
+        self.plot()
+
     @staticmethod
     @staticmethod
     def merge(geo_list, geo_final, multigeo=None):
     def merge(geo_list, geo_final, multigeo=None):
         """
         """

+ 18 - 680
AppObjects/FlatCAMGerber.py

@@ -115,23 +115,12 @@ class GerberObject(FlatCAMObj, Gerber):
             "plot": True,
             "plot": True,
             "multicolored": False,
             "multicolored": False,
             "solid": False,
             "solid": False,
-            "tool_type": 'circular',
-            "vtipdia": 0.1,
-            "vtipangle": 30,
-            "vcutz": -0.05,
-            "isotooldia": 0.016,
-            "isopasses": 1,
-            "isooverlap": 15,
-            "milling_type": "cl",
-            "combine_passes": True,
             "noncoppermargin": 0.0,
             "noncoppermargin": 0.0,
             "noncopperrounded": False,
             "noncopperrounded": False,
             "bboxmargin": 0.0,
             "bboxmargin": 0.0,
             "bboxrounded": False,
             "bboxrounded": False,
             "aperture_display": False,
             "aperture_display": False,
             "follow": False,
             "follow": False,
-            "iso_scope": 'all',
-            "iso_type": 'full'
         })
         })
 
 
         # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
         # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
@@ -197,33 +186,22 @@ class GerberObject(FlatCAMObj, Gerber):
             "plot": self.ui.plot_cb,
             "plot": self.ui.plot_cb,
             "multicolored": self.ui.multicolored_cb,
             "multicolored": self.ui.multicolored_cb,
             "solid": self.ui.solid_cb,
             "solid": self.ui.solid_cb,
-            "tool_type": self.ui.tool_type_radio,
-            "vtipdia": self.ui.tipdia_spinner,
-            "vtipangle": self.ui.tipangle_spinner,
-            "vcutz": self.ui.cutz_spinner,
-            "isotooldia": self.ui.iso_tool_dia_entry,
-            "isopasses": self.ui.iso_width_entry,
-            "isooverlap": self.ui.iso_overlap_entry,
-            "milling_type": self.ui.milling_type_radio,
-            "combine_passes": self.ui.combine_passes_cb,
             "noncoppermargin": self.ui.noncopper_margin_entry,
             "noncoppermargin": self.ui.noncopper_margin_entry,
             "noncopperrounded": self.ui.noncopper_rounded_cb,
             "noncopperrounded": self.ui.noncopper_rounded_cb,
             "bboxmargin": self.ui.bbmargin_entry,
             "bboxmargin": self.ui.bbmargin_entry,
             "bboxrounded": self.ui.bbrounded_cb,
             "bboxrounded": self.ui.bbrounded_cb,
             "aperture_display": self.ui.aperture_table_visibility_cb,
             "aperture_display": self.ui.aperture_table_visibility_cb,
-            "follow": self.ui.follow_cb,
-            "iso_scope": self.ui.iso_scope_radio,
-            "iso_type": self.ui.iso_type_radio
+            "follow": self.ui.follow_cb
         })
         })
 
 
         # Fill form fields only on object create
         # Fill form fields only on object create
         self.to_form()
         self.to_form()
 
 
         assert isinstance(self.ui, GerberObjectUI)
         assert isinstance(self.ui, GerberObjectUI)
+
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
         self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
         self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
         self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
-        self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
 
 
         # Tools
         # Tools
         self.ui.iso_button.clicked.connect(self.app.isolation_tool.run)
         self.ui.iso_button.clicked.connect(self.app.isolation_tool.run)
@@ -235,54 +213,17 @@ class GerberObject(FlatCAMObj, Gerber):
         self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
         self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
         self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
         self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
 
 
-        # set the model for the Area Exception comboboxes
-        self.ui.obj_combo.setModel(self.app.collection)
-        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.ui.obj_combo.is_last = True
-        self.ui.obj_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Geometry"): "Geometry"
-        }[self.ui.type_obj_combo.get_value()]
-        self.on_type_obj_index_changed()
-
-        self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
-
-        self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type_change)
-        # establish visibility for the GUI elements found in the slot function
-        self.ui.tool_type_radio.activated_custom.emit(self.options['tool_type'])
-
         # Show/Hide Advanced Options
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
         if self.app.defaults["global_app_level"] == 'b':
             self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
             self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
-            self.options['tool_type'] = 'circular'
-
-            self.ui.tool_type_label.hide()
-            self.ui.tool_type_radio.hide()
-
-            # override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1')
-            self.ui.tool_type_radio.set_value('circular')
-
-            self.ui.tipdialabel.hide()
-            self.ui.tipdia_spinner.hide()
-            self.ui.tipanglelabel.hide()
-            self.ui.tipangle_spinner.hide()
-            self.ui.cutzlabel.hide()
-            self.ui.cutz_spinner.hide()
 
 
             self.ui.apertures_table_label.hide()
             self.ui.apertures_table_label.hide()
             self.ui.aperture_table_visibility_cb.hide()
             self.ui.aperture_table_visibility_cb.hide()
-            self.ui.milling_type_label.hide()
-            self.ui.milling_type_radio.hide()
-            self.ui.iso_type_label.hide()
-            self.ui.iso_type_radio.hide()
 
 
             self.ui.follow_cb.hide()
             self.ui.follow_cb.hide()
-            self.ui.except_cb.setChecked(False)
-            self.ui.except_cb.hide()
+
         else:
         else:
             self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
             self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
-            self.ui.tipdia_spinner.valueChanged.connect(self.on_calculate_tooldia)
-            self.ui.tipangle_spinner.valueChanged.connect(self.on_calculate_tooldia)
-            self.ui.cutz_spinner.valueChanged.connect(self.on_calculate_tooldia)
 
 
         if self.app.defaults["gerber_buffering"] == 'no':
         if self.app.defaults["gerber_buffering"] == 'no':
             self.ui.create_buffer_button.show()
             self.ui.create_buffer_button.show()
@@ -300,58 +241,6 @@ class GerberObject(FlatCAMObj, Gerber):
         self.build_ui()
         self.build_ui()
         self.units_found = self.app.defaults['units']
         self.units_found = self.app.defaults['units']
 
 
-    def on_calculate_tooldia(self):
-        try:
-            tdia = float(self.ui.tipdia_spinner.get_value())
-        except Exception:
-            return
-        try:
-            dang = float(self.ui.tipangle_spinner.get_value())
-        except Exception:
-            return
-        try:
-            cutz = float(self.ui.cutz_spinner.get_value())
-        except Exception:
-            return
-
-        cutz *= -1
-        if cutz < 0:
-            cutz *= -1
-
-        half_tip_angle = dang / 2
-
-        tool_diameter = tdia + (2 * cutz * math.tan(math.radians(half_tip_angle)))
-        self.ui.iso_tool_dia_entry.set_value(tool_diameter)
-
-    def on_type_obj_index_changed(self):
-        val = self.ui.type_obj_combo.get_value()
-        obj_type = {"Gerber": 0, "Geometry": 2}[val]
-        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.ui.obj_combo.setCurrentIndex(0)
-        self.ui.obj_combo.obj_type = {_("Gerber"): "Gerber", _("Geometry"): "Geometry"}[val]
-
-    def on_tool_type_change(self, state):
-        if state == 'circular':
-            self.ui.tipdialabel.hide()
-            self.ui.tipdia_spinner.hide()
-            self.ui.tipanglelabel.hide()
-            self.ui.tipangle_spinner.hide()
-            self.ui.cutzlabel.hide()
-            self.ui.cutz_spinner.hide()
-            self.ui.iso_tool_dia_entry.setDisabled(False)
-            # update the value in the self.iso_tool_dia_entry once this is selected
-            self.ui.iso_tool_dia_entry.set_value(self.options['isotooldia'])
-        else:
-            self.ui.tipdialabel.show()
-            self.ui.tipdia_spinner.show()
-            self.ui.tipanglelabel.show()
-            self.ui.tipangle_spinner.show()
-            self.ui.cutzlabel.show()
-            self.ui.cutz_spinner.show()
-            self.ui.iso_tool_dia_entry.setDisabled(True)
-            # update the value in the self.iso_tool_dia_entry once this is selected
-            self.on_calculate_tooldia()
-
     def build_ui(self):
     def build_ui(self):
         FlatCAMObj.build_ui(self)
         FlatCAMObj.build_ui(self)
 
 
@@ -562,521 +451,6 @@ class GerberObject(FlatCAMObj, Gerber):
 
 
         self.app.app_obj.new_object("geometry", name, geo_init)
         self.app.app_obj.new_object("geometry", name, geo_init)
 
 
-    def on_iso_button_click(self, *args):
-
-        obj = self.app.collection.get_active()
-
-        self.iso_type = 2
-        if self.ui.iso_type_radio.get_value() == 'ext':
-            self.iso_type = 0
-        if self.ui.iso_type_radio.get_value() == 'int':
-            self.iso_type = 1
-
-        def worker_task(iso_obj, app_obj):
-            with self.app.proc_container.new(_("Isolating...")):
-                if self.ui.follow_cb.get_value() is True:
-                    iso_obj.follow_geo()
-                    # in the end toggle the visibility of the origin object so we can see the generated Geometry
-                    iso_obj.ui.plot_cb.toggle()
-                else:
-                    app_obj.defaults.report_usage("gerber_on_iso_button")
-                    self.read_form()
-
-                    iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single'
-                    self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
-
-        self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
-
-    def follow_geo(self, outname=None):
-        """
-        Creates a geometry object "following" the gerber paths.
-
-        :return: None
-        """
-
-        # default_name = self.options["name"] + "_follow"
-        # follow_name = outname or default_name
-
-        if outname is None:
-            follow_name = self.options["name"] + "_follow"
-        else:
-            follow_name = outname
-
-        def follow_init(follow_obj, app):
-            # Propagate options
-            follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
-            follow_obj.solid_geometry = self.follow_geometry
-
-        # TODO: Do something if this is None. Offer changing name?
-        try:
-            self.app.app_obj.new_object("geometry", follow_name, follow_init)
-        except Exception as e:
-            return "Operation failed: %s" % str(e)
-
-    def isolate_handler(self, iso_type, iso_scope):
-
-        if iso_scope == 'all':
-            self.isolate(iso_type=iso_type)
-        else:
-            # 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
-
-            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
-
-            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)
-
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
-
-    def on_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
-
-        try:
-            x = float(event_pos[0])
-            y = float(event_pos[1])
-        except TypeError:
-            return
-
-        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])
-
-        if event.button == 1:
-            clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
-
-            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."))
-                    )
-
-                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()
-
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.mr)
-
-            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                                  self.app.on_mouse_click_release_over_plot)
-
-            self.app.tool_shapes.clear(update=True)
-
-            if self.poly_dict:
-                poly_list = deepcopy(list(self.poly_dict.values()))
-                self.isolate(iso_type=self.iso_type, geometry=poly_list)
-                self.poly_dict.clear()
-            else:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
-
-    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])])
-
-        # delete previous selection shape
-        self.app.delete_selection_shape()
-
-        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
-
-        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."))
-
-    def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
-                milling_type=None, follow=None, plot=True):
-        """
-        Creates an isolation routing geometry object in the project.
-
-        :param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
-        :param geometry: specific geometry to isolate
-        :param dia: Tool diameter
-        :param passes: Number of tool widths to cut
-        :param overlap: Overlap between passes in fraction of tool diameter
-        :param outname: Base name of the output object
-        :param combine: Boolean: if to combine passes in one resulting object in case of multiple passes
-        :param milling_type: type of milling: conventional or climbing
-        :param follow: Boolean: if to generate a 'follow' geometry
-        :param plot: Boolean: if to plot the resulting geometry object
-        :return: None
-        """
-
-        if geometry is None:
-            work_geo = self.follow_geometry if follow is True else self.solid_geometry
-        else:
-            work_geo = geometry
-
-        if dia is None:
-            dia = float(self.options["isotooldia"])
-
-        if passes is None:
-            passes = int(self.options["isopasses"])
-
-        if overlap is None:
-            overlap = float(self.options["isooverlap"])
-
-        overlap /= 100.0
-
-        combine = self.options["combine_passes"] if combine is None else bool(combine)
-
-        if milling_type is None:
-            milling_type = self.options["milling_type"]
-
-        if iso_type is None:
-            iso_t = 2
-        else:
-            iso_t = iso_type
-
-        base_name = self.options["name"]
-
-        if combine:
-            if outname is None:
-                if self.iso_type == 0:
-                    iso_name = base_name + "_ext_iso"
-                elif self.iso_type == 1:
-                    iso_name = base_name + "_int_iso"
-                else:
-                    iso_name = base_name + "_iso"
-            else:
-                iso_name = outname
-
-            def iso_init(geo_obj, app_obj):
-                # Propagate options
-                geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
-                geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
-
-                geo_obj.solid_geometry = []
-
-                # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
-                if self.ui.tool_type_radio.get_value() == 'v':
-                    new_cutz = self.ui.cutz_spinner.get_value()
-                    new_vtipdia = self.ui.tipdia_spinner.get_value()
-                    new_vtipangle = self.ui.tipangle_spinner.get_value()
-                    tool_type = 'V'
-                else:
-                    new_cutz = self.app.defaults['geometry_cutz']
-                    new_vtipdia = self.app.defaults['geometry_vtipdia']
-                    new_vtipangle = self.app.defaults['geometry_vtipangle']
-                    tool_type = 'C1'
-
-                # store here the default data for Geometry Data
-                default_data = {}
-                default_data.update({
-                    "name": iso_name,
-                    "plot": self.app.defaults['geometry_plot'],
-                    "cutz": new_cutz,
-                    "vtipdia": new_vtipdia,
-                    "vtipangle": new_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'],
-                    "spindlespeed": self.app.defaults['geometry_spindlespeed'],
-                    "toolchangexy": self.app.defaults['geometry_toolchangexy'],
-                    "startz": self.app.defaults['geometry_startz']
-                })
-
-                geo_obj.tools = {}
-                geo_obj.tools['1'] = {}
-                geo_obj.tools.update({
-                    '1': {
-                        'tooldia': float(self.options["isotooldia"]),
-                        'offset': 'Path',
-                        'offset_value': 0.0,
-                        'type': _('Rough'),
-                        'tool_type': tool_type,
-                        'data': default_data,
-                        'solid_geometry': geo_obj.solid_geometry
-                    }
-                })
-
-                for nr_pass in range(passes):
-                    iso_offset = dia * ((2 * nr_pass + 1) / 2.0000001) - (nr_pass * overlap * dia)
-
-                    # if milling type is climb then the move is counter-clockwise around features
-                    mill_dir = 1 if milling_type == 'cl' else 0
-                    geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
-                                                  follow=follow, nr_passes=nr_pass)
-
-                    if geom == 'fail':
-                        app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
-                        return 'fail'
-                    geo_obj.solid_geometry.append(geom)
-
-                    # update the geometry in the tools
-                    geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry
-
-                # 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]
-
-                for g in geo_obj.solid_geometry:
-                    if g:
-                        break
-                    else:
-                        empty_cnt += 1
-
-                if empty_cnt == len(geo_obj.solid_geometry):
-                    raise ValidationError("Empty Geometry", None)
-                else:
-                    app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
-
-                # even if combine is checked, one pass is still single-geo
-                geo_obj.multigeo = True if passes > 1 else False
-
-                # ############################################################
-                # ########## 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)
-
-            # TODO: Do something if this is None. Offer changing name?
-            self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
-        else:
-            for i in range(passes):
-                offset = dia * ((2 * i + 1) / 2.0000001) - (i * overlap * dia)
-                if passes > 1:
-                    if outname is None:
-                        if self.iso_type == 0:
-                            iso_name = base_name + "_ext_iso" + str(i + 1)
-                        elif self.iso_type == 1:
-                            iso_name = base_name + "_int_iso" + str(i + 1)
-                        else:
-                            iso_name = base_name + "_iso" + str(i + 1)
-                    else:
-                        iso_name = outname
-                else:
-                    if outname is None:
-                        if self.iso_type == 0:
-                            iso_name = base_name + "_ext_iso"
-                        elif self.iso_type == 1:
-                            iso_name = base_name + "_int_iso"
-                        else:
-                            iso_name = base_name + "_iso"
-                    else:
-                        iso_name = outname
-
-                def iso_init(geo_obj, fc_obj):
-                    # Propagate options
-                    geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
-                    if self.ui.tool_type_radio.get_value() == 'v':
-                        geo_obj.tool_type = 'V'
-                    else:
-                        geo_obj.tool_type = 'C1'
-
-                    # if milling type is climb then the move is counter-clockwise around features
-                    mill_dir = 1 if milling_type == 'cl' else 0
-                    geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
-                                                  follow=follow,
-                                                  nr_passes=i)
-
-                    if geom == 'fail':
-                        fc_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
-                        return 'fail'
-
-                    geo_obj.solid_geometry = geom
-
-                    # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
-                    # even if the resulting geometry is not multigeo we add the tools dict which will hold the data
-                    # required to be transfered to the Geometry object
-                    if self.ui.tool_type_radio.get_value() == 'v':
-                        new_cutz = self.ui.cutz_spinner.get_value()
-                        new_vtipdia = self.ui.tipdia_spinner.get_value()
-                        new_vtipangle = self.ui.tipangle_spinner.get_value()
-                        tool_type = 'V'
-                    else:
-                        new_cutz = self.app.defaults['geometry_cutz']
-                        new_vtipdia = self.app.defaults['geometry_vtipdia']
-                        new_vtipangle = self.app.defaults['geometry_vtipangle']
-                        tool_type = 'C1'
-
-                    # store here the default data for Geometry Data
-                    default_data = {}
-                    default_data.update({
-                        "name": iso_name,
-                        "plot": self.app.defaults['geometry_plot'],
-                        "cutz": new_cutz,
-                        "vtipdia": new_vtipdia,
-                        "vtipangle": new_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'],
-                        "spindlespeed": self.app.defaults['geometry_spindlespeed'],
-                        "toolchangexy": self.app.defaults['geometry_toolchangexy'],
-                        "startz": self.app.defaults['geometry_startz']
-                    })
-
-                    geo_obj.tools = {}
-                    geo_obj.tools['1'] = {}
-                    geo_obj.tools.update({
-                        '1': {
-                            'tooldia': float(self.options["isotooldia"]),
-                            'offset': 'Path',
-                            'offset_value': 0.0,
-                            'type': _('Rough'),
-                            'tool_type': tool_type,
-                            'data': default_data,
-                            'solid_geometry': geo_obj.solid_geometry
-                        }
-                    })
-
-                    # 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]
-
-                    for g in geo_obj.solid_geometry:
-                        if g:
-                            break
-                        else:
-                            empty_cnt += 1
-
-                    if empty_cnt == len(geo_obj.solid_geometry):
-                        raise ValidationError("Empty Geometry", None)
-                    else:
-                        fc_obj.inform.emit('[success] %s: %s' %
-                                            (_("Isolation geometry created"), geo_obj.options["name"]))
-                    geo_obj.multigeo = False
-
-                    # ############################################################
-                    # ########## 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)
-
-                # TODO: Do something if this is None. Offer changing name?
-                self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
-
     def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
     def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
         # isolation_geometry produces an envelope that is going on the left of the geometry
         # 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 copper features). To leave the least amount of burrs on the features
@@ -1117,64 +491,28 @@ class GerberObject(FlatCAMObj, Gerber):
                 return 'fail'
                 return 'fail'
         return geom
         return geom
 
 
-    def area_subtraction(self, geo, subtractor_geo=None):
+    def follow_geo(self, outname=None):
         """
         """
-        Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo
+        Creates a geometry object "following" the gerber paths.
 
 
-        :param geo: target geometry from which to subtract
-        :param subtractor_geo: geometry that acts as subtractor
-        :return:
+        :return: None
         """
         """
-        new_geometry = []
-        target_geo = geo
 
 
-        if subtractor_geo:
-            sub_union = cascaded_union(subtractor_geo)
+        if outname is None:
+            follow_name = self.options["name"] + "_follow"
         else:
         else:
-            name = self.ui.obj_combo.currentText()
-            subtractor_obj = self.app.collection.get_by_name(name)
-            sub_union = cascaded_union(subtractor_obj.solid_geometry)
+            follow_name = outname
+
+        def follow_init(follow_obj, app):
+            # Propagate options
+            follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
+            follow_obj.solid_geometry = self.follow_geometry
 
 
+        # TODO: Do something if this is None. Offer changing name?
         try:
         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):
-                    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):
-                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
+            self.app.app_obj.new_object("geometry", follow_name, follow_init)
+        except Exception as e:
+            return "Operation failed: %s" % str(e)
 
 
     def on_plot_cb_click(self, *args):
     def on_plot_cb_click(self, *args):
         if self.muted_ui:
         if self.muted_ui:

+ 13 - 13
AppObjects/FlatCAMObj.py

@@ -459,20 +459,20 @@ class FlatCAMObj(QtCore.QObject):
     def visible(self, value, threaded=True):
     def visible(self, value, threaded=True):
         log.debug("FlatCAMObj.visible()")
         log.debug("FlatCAMObj.visible()")
 
 
-        def worker_task(app_obj):
-            self.shapes.visible = value
-
-            if self.app.is_legacy is False:
-                # Not all object types has annotations
-                try:
-                    self.annotation.visible = value
-                except Exception:
-                    pass
-
-        if threaded is False:
-            worker_task(app_obj=self.app)
+        # self.shapes.visible = value   # maybe this is slower in VisPy? use enabled property?
+        if self.shapes.visible is True:
+            if value is False:
+                self.shapes.visible = False
         else:
         else:
-            self.app.worker_task.emit({'fcn': worker_task, 'params': [self]})
+            if value is True:
+                self.shapes.visible = True
+
+        if self.app.is_legacy is False:
+            # Not all object types has annotations
+            try:
+                self.annotation.visible = value
+            except Exception:
+                pass
 
 
     @property
     @property
     def drawing_tolerance(self):
     def drawing_tolerance(self):

+ 3 - 3
AppObjects/ObjectCollection.py

@@ -1185,7 +1185,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                 except Exception:
                 except Exception:
                     pass
                     pass
             if obj_list:
             if obj_list:
-                self.app.inform.emit('[selected] %s' % _("All objects are selected."))
+                self.app.inform[str, bool].emit('[selected] %s' % _("All objects are selected."), False)
         else:
         else:
             self.set_all_inactive()
             self.set_all_inactive()
             for act in self.app.ui.menuobjects.actions():
             for act in self.app.ui.menuobjects.actions():
@@ -1195,6 +1195,6 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                     pass
                     pass
 
 
             if obj_list:
             if obj_list:
-                self.app.inform.emit('%s' % _("Objects selection is cleared."))
+                self.app.inform[str, bool].emit('%s' % _("Objects selection is cleared."), False)
             else:
             else:
-                self.app.inform.emit('')
+                self.app.inform[str, bool].emit('', False)

+ 9 - 5
AppTool.py

@@ -277,16 +277,20 @@ class AppTool(QtWidgets.QWidget):
 
 
     def confirmation_message(self, accepted, minval, maxval):
     def confirmation_message(self, accepted, minval, maxval):
         if accepted is False:
         if accepted is False:
-            self.app.inform.emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' %
-                                 (_("Edited value is out of range"), self.decimals, minval, self.decimals, maxval))
+            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:
         else:
-            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
 
     def confirmation_message_int(self, accepted, minval, maxval):
     def confirmation_message_int(self, accepted, minval, maxval):
         if accepted is False:
         if accepted is False:
-            self.app.inform.emit('[WARNING_NOTCL] %s: [%d, %d]' % (_("Edited value is out of range"), minval, maxval))
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                            (_("Edited value is out of range"), minval, maxval), False)
         else:
         else:
-            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
 
     def sizeHint(self):
     def sizeHint(self):
         """
         """

+ 14 - 6
AppTools/ToolCutOut.py

@@ -501,6 +501,13 @@ class CutOut(AppTool):
             "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
             "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
             "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
             "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
             "tools_paintcontour":       self.app.defaults["tools_paintcontour"],
             "tools_paintcontour":       self.app.defaults["tools_paintcontour"],
+
+            # Isolation Tool
+            "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"],
         })
         })
 
 
     def on_freeform_cutout(self):
     def on_freeform_cutout(self):
@@ -912,7 +919,7 @@ class CutOut(AppTool):
         if 0 in {self.cutting_dia}:
         if 0 in {self.cutting_dia}:
             self.app.inform.emit('[ERROR_NOTCL] %s' %
             self.app.inform.emit('[ERROR_NOTCL] %s' %
                                  _("Tool Diameter is zero value. Change it to a positive real number."))
                                  _("Tool Diameter is zero value. Change it to a positive real number."))
-            return "Tool Diameter is zero value. Change it to a positive real number."
+            return
 
 
         self.cutting_gapsize = float(self.gapsize.get_value())
         self.cutting_gapsize = float(self.gapsize.get_value())
 
 
@@ -923,7 +930,7 @@ class CutOut(AppTool):
         except Exception as e:
         except Exception as e:
             log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
             log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve Geometry object"), name))
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve Geometry object"), name))
-            return "Could not retrieve object: %s" % name
+            return
 
 
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
             self.app.plotcanvas.graph_event_disconnect('key_press', self.app.ui.keyPressEvent)
             self.app.plotcanvas.graph_event_disconnect('key_press', self.app.ui.keyPressEvent)
@@ -1306,10 +1313,10 @@ class CutOut(AppTool):
         This only operates on the paths in the original geometry,
         This only operates on the paths in the original geometry,
         i.e. it converts polygons into paths.
         i.e. it converts polygons into paths.
 
 
-        :param x0: x coord for lower left vertice of the polygon.
-        :param y0: y coord for lower left vertice of the polygon.
-        :param x1: x coord for upper right vertice of the polygon.
-        :param y1: y coord for upper right vertice of the polygon.
+        :param x0: x coord for lower left vertex of the polygon.
+        :param y0: y coord for lower left vertex of the polygon.
+        :param x1: x coord for upper right vertex of the polygon.
+        :param y1: y coord for upper right vertex of the polygon.
 
 
         :param solid_geo: Geometry from which to substract. If none, use the solid_geomety property of the object
         :param solid_geo: Geometry from which to substract. If none, use the solid_geomety property of the object
         :return: none
         :return: none
@@ -1367,6 +1374,7 @@ def flatten(geometry):
 
 
 def recursive_bounds(geometry):
 def recursive_bounds(geometry):
     """
     """
+    Return the bounds of the biggest bounding box in geometry, one that include all.
 
 
     :param geometry:    a iterable object that holds geometry
     :param geometry:    a iterable object that holds geometry
     :return:            Returns coordinates of rectangular bounds of geometry: (xmin, ymin, xmax, ymax).
     :return:            Returns coordinates of rectangular bounds of geometry: (xmin, ymin, xmax, ymax).

+ 71 - 16
AppTools/ToolEtchCompensation.py

@@ -13,6 +13,7 @@ from AppGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox,
 from shapely.ops import unary_union
 from shapely.ops import unary_union
 
 
 from copy import deepcopy
 from copy import deepcopy
+import math
 
 
 import logging
 import logging
 import gettext
 import gettext
@@ -95,7 +96,7 @@ class ToolEtchCompensation(AppTool):
 
 
         hlay_1 = QtWidgets.QHBoxLayout()
         hlay_1 = QtWidgets.QHBoxLayout()
 
 
-        self.oz_entry = NumericalEvalEntry()
+        self.oz_entry = NumericalEvalEntry(border_color='#0069A9')
         self.oz_entry.setPlaceholderText(_("Oz value"))
         self.oz_entry.setPlaceholderText(_("Oz value"))
         self.oz_to_um_entry = FCEntry()
         self.oz_to_um_entry = FCEntry()
         self.oz_to_um_entry.setPlaceholderText(_("Microns value"))
         self.oz_to_um_entry.setPlaceholderText(_("Microns value"))
@@ -116,7 +117,7 @@ class ToolEtchCompensation(AppTool):
 
 
         hlay_2 = QtWidgets.QHBoxLayout()
         hlay_2 = QtWidgets.QHBoxLayout()
 
 
-        self.mils_entry = NumericalEvalEntry()
+        self.mils_entry = NumericalEvalEntry(border_color='#0069A9')
         self.mils_entry.setPlaceholderText(_("Mils value"))
         self.mils_entry.setPlaceholderText(_("Mils value"))
         self.mils_to_um_entry = FCEntry()
         self.mils_to_um_entry = FCEntry()
         self.mils_to_um_entry.setPlaceholderText(_("Microns value"))
         self.mils_to_um_entry.setPlaceholderText(_("Microns value"))
@@ -155,8 +156,9 @@ class ToolEtchCompensation(AppTool):
               "- preselection -> value which depends on a selection of etchants")
               "- preselection -> value which depends on a selection of etchants")
         )
         )
         self.ratio_radio = RadioSet([
         self.ratio_radio = RadioSet([
-            {'label': _('Custom'), 'value': 'c'},
-            {'label': _('PreSelection'), 'value': 'p'}
+            {'label': _('Etch Factor'), 'value': 'factor'},
+            {'label': _('Etchants list'), 'value': 'etch_list'},
+            {'label': _('Manual offset'), 'value': 'manual'}
         ], orientation='vertical', stretch=False)
         ], orientation='vertical', stretch=False)
 
 
         grid0.addWidget(self.ratio_label, 14, 0, 1, 2)
         grid0.addWidget(self.ratio_label, 14, 0, 1, 2)
@@ -180,18 +182,34 @@ class ToolEtchCompensation(AppTool):
             _("The ratio between depth etch and lateral etch .\n"
             _("The ratio between depth etch and lateral etch .\n"
               "Accepts real numbers and formulas using the operators: /,*,+,-,%")
               "Accepts real numbers and formulas using the operators: /,*,+,-,%")
         )
         )
-        self.factor_entry = NumericalEvalEntry()
+        self.factor_entry = NumericalEvalEntry(border_color='#0069A9')
         self.factor_entry.setPlaceholderText(_("Real number or formula"))
         self.factor_entry.setPlaceholderText(_("Real number or formula"))
         self.factor_entry.setObjectName(_("Etch_factor"))
         self.factor_entry.setObjectName(_("Etch_factor"))
 
 
+        grid0.addWidget(self.factor_label, 19, 0)
+        grid0.addWidget(self.factor_entry, 19, 1)
+
+        # Manual Offset
+        self.offset_label = QtWidgets.QLabel('%s:' % _('Offset'))
+        self.offset_label.setToolTip(
+            _("Value with which to increase or decrease (buffer)\n"
+              "the copper features. In microns [um].")
+        )
+        self.offset_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.offset_entry.set_precision(self.decimals)
+        self.offset_entry.set_range(-9999.9999, 9999.9999)
+        self.offset_entry.setObjectName(_("Offset"))
+
+        grid0.addWidget(self.offset_label, 20, 0)
+        grid0.addWidget(self.offset_entry, 20, 1)
+
         # Hide the Etchants and Etch factor
         # Hide the Etchants and Etch factor
         self.etchants_label.hide()
         self.etchants_label.hide()
         self.etchants_combo.hide()
         self.etchants_combo.hide()
         self.factor_label.hide()
         self.factor_label.hide()
         self.factor_entry.hide()
         self.factor_entry.hide()
-
-        grid0.addWidget(self.factor_label, 20, 0)
-        grid0.addWidget(self.factor_entry, 20, 1)
+        self.offset_label.hide()
+        self.offset_entry.hide()
 
 
         separator_line = QtWidgets.QFrame()
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -265,7 +283,7 @@ class ToolEtchCompensation(AppTool):
 
 
     def set_tool_ui(self):
     def set_tool_ui(self):
         self.thick_entry.set_value(18.0)
         self.thick_entry.set_value(18.0)
-        self.ratio_radio.set_value('c')
+        self.ratio_radio.set_value('factor')
 
 
     def on_ratio_change(self, val):
     def on_ratio_change(self, val):
         """
         """
@@ -276,16 +294,27 @@ class ToolEtchCompensation(AppTool):
         :return:        None
         :return:        None
         :rtype:
         :rtype:
         """
         """
-        if val == 'c':
+        if val == 'factor':
             self.etchants_label.hide()
             self.etchants_label.hide()
             self.etchants_combo.hide()
             self.etchants_combo.hide()
             self.factor_label.show()
             self.factor_label.show()
             self.factor_entry.show()
             self.factor_entry.show()
-        else:
+            self.offset_label.hide()
+            self.offset_entry.hide()
+        elif val == 'etch_list':
             self.etchants_label.show()
             self.etchants_label.show()
             self.etchants_combo.show()
             self.etchants_combo.show()
             self.factor_label.hide()
             self.factor_label.hide()
             self.factor_entry.hide()
             self.factor_entry.hide()
+            self.offset_label.hide()
+            self.offset_entry.hide()
+        else:
+            self.etchants_label.hide()
+            self.etchants_combo.hide()
+            self.factor_label.hide()
+            self.factor_entry.hide()
+            self.offset_label.show()
+            self.offset_entry.show()
 
 
     def on_oz_conversion(self, txt):
     def on_oz_conversion(self, txt):
         try:
         try:
@@ -294,6 +323,7 @@ class ToolEtchCompensation(AppTool):
             # mils to microns by multiplying with 25.4
             # mils to microns by multiplying with 25.4
             val *= 34.798
             val *= 34.798
         except Exception:
         except Exception:
+            self.oz_to_um_entry.set_value('')
             return
             return
         self.oz_to_um_entry.set_value(val, self.decimals)
         self.oz_to_um_entry.set_value(val, self.decimals)
 
 
@@ -302,10 +332,13 @@ class ToolEtchCompensation(AppTool):
             val = eval(txt)
             val = eval(txt)
             val *= 25.4
             val *= 25.4
         except Exception:
         except Exception:
+            self.mils_to_um_entry.set_value('')
             return
             return
         self.mils_to_um_entry.set_value(val, self.decimals)
         self.mils_to_um_entry.set_value(val, self.decimals)
 
 
     def on_compensate(self):
     def on_compensate(self):
+        log.debug("ToolEtchCompensation.on_compensate()")
+
         ratio_type = self.ratio_radio.get_value()
         ratio_type = self.ratio_radio.get_value()
         thickness = self.thick_entry.get_value() / 1000     # in microns
         thickness = self.thick_entry.get_value() / 1000     # in microns
 
 
@@ -327,15 +360,18 @@ class ToolEtchCompensation(AppTool):
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
             return
             return
 
 
-        if ratio_type == 'c':
+        if ratio_type == 'factor':
             etch_factor = 1 / self.factor_entry.get_value()
             etch_factor = 1 / self.factor_entry.get_value()
-        else:
+            offset = thickness / etch_factor
+        elif ratio_type == 'etch_list':
             etchant = self.etchants_combo.get_value()
             etchant = self.etchants_combo.get_value()
             if etchant == "CuCl2":
             if etchant == "CuCl2":
                 etch_factor = 0.33
                 etch_factor = 0.33
             else:
             else:
                 etch_factor = 0.25
                 etch_factor = 0.25
-        offset = thickness / etch_factor
+            offset = thickness / etch_factor
+        else:
+            offset = self.offset_entry.get_value() / 1000   # in microns
 
 
         try:
         try:
             __ = iter(grb_obj.solid_geometry)
             __ = iter(grb_obj.solid_geometry)
@@ -354,13 +390,32 @@ class ToolEtchCompensation(AppTool):
 
 
         new_apertures = deepcopy(grb_obj.apertures)
         new_apertures = deepcopy(grb_obj.apertures)
 
 
+        # update the apertures attributes (keys in the apertures dict)
         for ap in new_apertures:
         for ap in new_apertures:
-            for k in ap:
+            type = new_apertures[ap]['type']
+            for k in new_apertures[ap]:
+                if type == 'R' or type == 'O':
+                    if k == 'width' or k == 'height':
+                        new_apertures[ap][k] += offset
+                else:
+                    if k == 'size' or k == 'width' or k == 'height':
+                        new_apertures[ap][k] += offset
+
                 if k == 'geometry':
                 if k == 'geometry':
-                    for geo_el in new_apertures[ap]['geometry']:
+                    for geo_el in new_apertures[ap][k]:
                         if 'solid' in geo_el:
                         if 'solid' in geo_el:
                             geo_el['solid'] = geo_el['solid'].buffer(offset, int(grb_circle_steps))
                             geo_el['solid'] = geo_el['solid'].buffer(offset, int(grb_circle_steps))
 
 
+        # in case of 'R' or 'O' aperture type we need to update the aperture 'size' after
+        # the 'width' and 'height' keys were updated
+        for ap in new_apertures:
+            type = new_apertures[ap]['type']
+            for k in new_apertures[ap]:
+                if type == 'R' or type == 'O':
+                    if k == 'size':
+                        new_apertures[ap][k] = math.sqrt(
+                            new_apertures[ap]['width'] ** 2 + new_apertures[ap]['height'] ** 2)
+
         def init_func(new_obj, app_obj):
         def init_func(new_obj, app_obj):
             """
             """
             Init a new object in FlatCAM Object collection
             Init a new object in FlatCAM Object collection

+ 2 - 2
AppTools/ToolFilm.py

@@ -729,7 +729,7 @@ class Film(AppTool):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export positive film"),
                 caption=_("Export positive film"),
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
-                filter=filter_ext)
+                ext_filter=filter_ext)
         except TypeError:
         except TypeError:
             filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export positive film"))
             filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export positive film"))
 
 
@@ -875,7 +875,7 @@ class Film(AppTool):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export negative film"),
                 caption=_("Export negative film"),
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
-                filter=filter_ext)
+                ext_filter=filter_ext)
         except TypeError:
         except TypeError:
             filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export negative film"))
             filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export negative film"))
 
 

Разница между файлами не показана из-за своего большого размера
+ 341 - 396
AppTools/ToolIsolation.py


+ 7 - 5
AppTools/ToolNCC.py

@@ -509,7 +509,7 @@ class NonCopperClear(AppTool, Gerber):
         self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
         self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
 
 
         # Rest Machining
         # Rest Machining
-        self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining"))
+        self.ncc_rest_cb = FCCheckBox('%s' % _("Rest"))
         self.ncc_rest_cb.setObjectName("n_rest_machining")
         self.ncc_rest_cb.setObjectName("n_rest_machining")
 
 
         self.ncc_rest_cb.setToolTip(
         self.ncc_rest_cb.setToolTip(
@@ -1621,10 +1621,12 @@ class NonCopperClear(AppTool, Gerber):
                                                                     "use a number."))
                                                                     "use a number."))
                         continue
                         continue
 
 
-                if self.op_radio.get_value() == _("Isolation"):
-                    self.iso_dia_list.append(self.tooldia)
-                else:
-                    self.ncc_dia_list.append(self.tooldia)
+                for uid_k, uid_v in self.ncc_tools.items():
+                    if round(uid_v['tooldia'], self.decimals) == round(self.tooldia, self.decimals):
+                        if uid_v['data']['tools_nccoperation'] == "iso":
+                            self.iso_dia_list.append(self.tooldia)
+                        else:
+                            self.ncc_dia_list.append(self.tooldia)
         else:
         else:
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
             return
             return

+ 3 - 3
AppTools/ToolPaint.py

@@ -438,7 +438,7 @@ class ToolPaint(AppTool, Gerber):
         )
         )
         grid4.addWidget(self.gen_param_label, 15, 0, 1, 2)
         grid4.addWidget(self.gen_param_label, 15, 0, 1, 2)
 
 
-        self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
+        self.rest_cb = FCCheckBox('%s' % _("Rest"))
         self.rest_cb.setObjectName('p_rest_machining')
         self.rest_cb.setObjectName('p_rest_machining')
         self.rest_cb.setToolTip(
         self.rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
             _("If checked, use 'rest machining'.\n"
@@ -482,7 +482,7 @@ class ToolPaint(AppTool, Gerber):
 
 
         self.selectmethod_combo = FCComboBox()
         self.selectmethod_combo = FCComboBox()
         self.selectmethod_combo.addItems(
         self.selectmethod_combo.addItems(
-            [_("Polygon Selection"), _("Area Selection"), _("All Polygons"), _("Reference Object")]
+            [_("Polygon Selection"), _("Area Selection"), _("All"), _("Reference Object")]
         )
         )
         self.selectmethod_combo.setObjectName('p_selection')
         self.selectmethod_combo.setObjectName('p_selection')
 
 
@@ -1423,7 +1423,7 @@ class ToolPaint(AppTool, Gerber):
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
             return
             return
 
 
-        if self.select_method == _("All Polygons"):
+        if self.select_method == _("All"):
             self.paint_poly_all(self.paint_obj,
             self.paint_poly_all(self.paint_obj,
                                 tooldia=self.tooldia_list,
                                 tooldia=self.tooldia_list,
                                 outname=self.o_name)
                                 outname=self.o_name)

+ 4 - 4
AppTools/ToolQRCode.py

@@ -781,9 +781,9 @@ class QRCode(AppTool):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export PNG"),
                 caption=_("Export PNG"),
                 directory=self.app.get_last_save_folder() + '/' + str(name) + '_png',
                 directory=self.app.get_last_save_folder() + '/' + str(name) + '_png',
-                filter=_filter)
+                ext_filter=_filter)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export PNG"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export PNG"), ext_filter=_filter)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -828,9 +828,9 @@ class QRCode(AppTool):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export SVG"),
                 caption=_("Export SVG"),
                 directory=self.app.get_last_save_folder() + '/' + str(name) + '_svg',
                 directory=self.app.get_last_save_folder() + '/' + str(name) + '_svg',
-                filter=_filter)
+                ext_filter=_filter)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export SVG"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export SVG"), ext_filter=_filter)
 
 
         filename = str(filename)
         filename = str(filename)
 
 

+ 7 - 7
AppTools/ToolRulesCheck.py

@@ -951,7 +951,7 @@ class RulesCheck(AppTool):
                                 geo = geo_el['solid']
                                 geo = geo_el['solid']
                                 pt = geo.representative_point()
                                 pt = geo.representative_point()
                                 points_list.append((pt.x, pt.y))
                                 points_list.append((pt.x, pt.y))
-                except Exception as e:
+                except Exception:
                     # An exception  will be raised for the 'size' key in case of apertures of type AM (macro) which does
                     # An exception  will be raised for the 'size' key in case of apertures of type AM (macro) which does
                     # not have the size key
                     # not have the size key
                     pass
                     pass
@@ -1137,7 +1137,7 @@ class RulesCheck(AppTool):
                     copper_list.append(elem_dict)
                     copper_list.append(elem_dict)
 
 
                 copper_name_2 = self.copper_b_object.currentText()
                 copper_name_2 = self.copper_b_object.currentText()
-                if copper_name_2 !='' and self.copper_b_cb.get_value():
+                if copper_name_2 != '' and self.copper_b_cb.get_value():
                     elem_dict = {}
                     elem_dict = {}
                     elem_dict['name'] = deepcopy(copper_name_2)
                     elem_dict['name'] = deepcopy(copper_name_2)
                     elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_2).apertures)
                     elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_2).apertures)
@@ -1363,12 +1363,12 @@ class RulesCheck(AppTool):
                     top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures)
                     top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures)
 
 
                 silk_bottom = self.ss_b_object.currentText()
                 silk_bottom = self.ss_b_object.currentText()
-                if silk_bottom !=  '' and self.ss_b_cb.get_value():
+                if silk_bottom != '' and self.ss_b_cb.get_value():
                     bottom_dict['name'] = deepcopy(silk_bottom)
                     bottom_dict['name'] = deepcopy(silk_bottom)
                     bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures)
                     bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures)
 
 
                 copper_outline = self.outline_object.currentText()
                 copper_outline = self.outline_object.currentText()
-                if copper_outline !=  '' and self.out_cb.get_value():
+                if copper_outline != '' and self.out_cb.get_value():
                     outline_dict['name'] = deepcopy(copper_outline)
                     outline_dict['name'] = deepcopy(copper_outline)
                     outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures)
                     outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures)
 
 
@@ -1421,7 +1421,7 @@ class RulesCheck(AppTool):
 
 
                 if self.sm_t_cb.get_value():
                 if self.sm_t_cb.get_value():
                     solder_obj = self.sm_t_object.currentText()
                     solder_obj = self.sm_t_object.currentText()
-                    if solder_obj !=  '':
+                    if solder_obj != '':
                         sm_dict['name'] = deepcopy(solder_obj)
                         sm_dict['name'] = deepcopy(solder_obj)
                         sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
                         sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
 
 
@@ -1431,7 +1431,7 @@ class RulesCheck(AppTool):
                                                                         _("TOP -> Minimum Solder Mask Sliver"))))
                                                                         _("TOP -> Minimum Solder Mask Sliver"))))
                 if self.sm_b_cb.get_value():
                 if self.sm_b_cb.get_value():
                     solder_obj = self.sm_b_object.currentText()
                     solder_obj = self.sm_b_object.currentText()
-                    if solder_obj !=  '':
+                    if solder_obj != '':
                         sm_dict['name'] = deepcopy(solder_obj)
                         sm_dict['name'] = deepcopy(solder_obj)
                         sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
                         sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
 
 
@@ -1625,7 +1625,7 @@ class RulesCheck(AppTool):
             new_obj.source_file = txt
             new_obj.source_file = txt
             new_obj.read_only = True
             new_obj.read_only = True
 
 
-        self.app.app_obj.new_object('document', name='Rules Check results', initialize=init, plot=False)
+        self.app.app_obj.new_object('document', name='Rules_check_results', initialize=init, plot=False)
 
 
     def reset_fields(self):
     def reset_fields(self):
         # self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         # self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 12 - 12
AppTools/ToolShell.py

@@ -267,6 +267,7 @@ class FCShell(TermWidget):
         self.app = app
         self.app = app
 
 
         self.tcl_commands_storage = {}
         self.tcl_commands_storage = {}
+        self.tcl = None
 
 
         self.init_tcl()
         self.init_tcl()
 
 
@@ -345,11 +346,11 @@ class FCShell(TermWidget):
 
 
     def is_command_complete(self, text):
     def is_command_complete(self, text):
 
 
-        def skipQuotes(txt):
-            quote = txt[0]
-            text_val = txt[1:]
-            endIndex = str(text_val).index(quote)
-            return text[endIndex:]
+        # def skipQuotes(txt):
+        #     quote = txt[0]
+        #     text_val = txt[1:]
+        #     endIndex = str(text_val).index(quote)
+        #     return text[endIndex:]
 
 
         # I'm disabling this because I need to be able to load paths that have spaces by
         # I'm disabling this because I need to be able to load paths that have spaces by
         # enclosing them in quotes --- Marius Stanciu
         # enclosing them in quotes --- Marius Stanciu
@@ -371,10 +372,10 @@ class FCShell(TermWidget):
         Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
         Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
         Also handles execution in separated threads
         Also handles execution in separated threads
 
 
-        :param text: FlatCAM TclCommand with parameters
-        :param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
-        will create crashes of the _Expandable_Edit widget
-        :return: output if there was any
+        :param text:        FlatCAM TclCommand with parameters
+        :param no_echo:     If True it will not try to print to the Shell because most likely the shell is hidden and it
+                            will create crashes of the _Expandable_Edit widget
+        :return:            output if there was any
         """
         """
 
 
         self.app.defaults.report_usage('exec_command')
         self.app.defaults.report_usage('exec_command')
@@ -404,12 +405,11 @@ class FCShell(TermWidget):
                 self.append_output(result + '\n')
                 self.append_output(result + '\n')
 
 
         except tk.TclError as e:
         except tk.TclError as e:
-
             # This will display more precise answer if something in TCL shell fails
             # This will display more precise answer if something in TCL shell fails
             result = self.tcl.eval("set errorInfo")
             result = self.tcl.eval("set errorInfo")
-            self.app.log.error("Exec command Exception: %s" % (result + '\n'))
+            self.app.log.error("Exception on Tcl Command execution: %s" % (result + '\n'))
             if no_echo is False:
             if no_echo is False:
-                self.append_error('ERROR: ' + result + '\n')
+                self.append_error('ERROR Report: ' + result + '\n')
             # Show error in console and just return or in test raise exception
             # Show error in console and just return or in test raise exception
             if reraise:
             if reraise:
                 raise e
                 raise e

+ 3 - 2
AppTools/ToolSolderPaste.py

@@ -1493,10 +1493,11 @@ class SolderPaste(AppTool):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export GCode ..."),
                 caption=_("Export GCode ..."),
                 directory=dir_file_to_save,
                 directory=dir_file_to_save,
-                filter=_filter_
+                ext_filter=_filter_
             )
             )
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), filter=_filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export Machine Code ..."), ext_filter=_filter_)
 
 
         if filename == '':
         if filename == '':
             self.app.inform.emit('[WARNING_NOTCL] %s' %
             self.app.inform.emit('[WARNING_NOTCL] %s' %

+ 130 - 131
AppTools/ToolSub.py

@@ -32,6 +32,11 @@ class ToolSub(AppTool):
 
 
     job_finished = QtCore.pyqtSignal(bool)
     job_finished = QtCore.pyqtSignal(bool)
 
 
+    # the string param is the outname and the list is a list of tuples each being formed from the new_aperture_geometry
+    # list and the second element is also a list with possible geometry that needs to be added to the '0' aperture
+    # meaning geometry that was deformed
+    aperture_processing_finished = QtCore.pyqtSignal(str, list)
+
     toolName = _("Subtract Tool")
     toolName = _("Subtract Tool")
 
 
     def __init__(self, app):
     def __init__(self, app):
@@ -218,18 +223,14 @@ class ToolSub(AppTool):
 
 
         self.sub_union = []
         self.sub_union = []
 
 
-        try:
-            self.intersect_btn.clicked.disconnect(self.on_grb_intersection_click)
-        except (TypeError, AttributeError):
-            pass
-        self.intersect_btn.clicked.connect(self.on_grb_intersection_click)
+        # multiprocessing
+        self.pool = self.app.pool
+        self.results = []
 
 
-        try:
-            self.intersect_geo_btn.clicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
+        self.intersect_btn.clicked.connect(self.on_grb_intersection_click)
         self.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click)
         self.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click)
         self.job_finished.connect(self.on_job_finished)
         self.job_finished.connect(self.on_job_finished)
+        self.aperture_processing_finished.connect(self.new_gerber_object)
         self.reset_button.clicked.connect(self.set_tool_ui)
         self.reset_button.clicked.connect(self.set_tool_ui)
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):
@@ -310,138 +311,141 @@ class ToolSub(AppTool):
         # crate the new_apertures dict structure
         # crate the new_apertures dict structure
         for apid in self.target_grb_obj.apertures:
         for apid in self.target_grb_obj.apertures:
             self.new_apertures[apid] = {}
             self.new_apertures[apid] = {}
-            self.new_apertures[apid]['type'] = 'C'
-            self.new_apertures[apid]['size'] = self.target_grb_obj.apertures[apid]['size']
-            self.new_apertures[apid]['geometry'] = []
-
-        geo_solid_union_list = []
-        geo_follow_union_list = []
-        geo_clear_union_list = []
-
-        for apid1 in self.sub_grb_obj.apertures:
-            if 'geometry' in self.sub_grb_obj.apertures[apid1]:
-                for elem in self.sub_grb_obj.apertures[apid1]['geometry']:
-                    if 'solid' in elem:
-                        geo_solid_union_list.append(elem['solid'])
-                    if 'follow' in elem:
-                        geo_follow_union_list.append(elem['follow'])
-                    if 'clear' in elem:
-                        geo_clear_union_list.append(elem['clear'])
+            for key in self.target_grb_obj.apertures[apid]:
+                if key == 'geometry':
+                    self.new_apertures[apid]['geometry'] = []
+                else:
+                    self.new_apertures[apid][key] = self.target_grb_obj.apertures[apid][key]
 
 
-        self.app.inform.emit('%s' % _("Processing geometry from Subtractor Gerber object."))
-        self.sub_solid_union = cascaded_union(geo_solid_union_list)
-        self.sub_follow_union = cascaded_union(geo_follow_union_list)
-        self.sub_clear_union = cascaded_union(geo_clear_union_list)
+        def worker_job(app_obj):
+            for apid in self.target_grb_obj.apertures:
+                target_geo = self.target_grb_obj.apertures[apid]['geometry']
 
 
-        # add the promises
-        for apid in self.target_grb_obj.apertures:
-            self.promises.append(apid)
+                sub_geometry = {}
+                sub_geometry['solid'] = []
+                sub_geometry['clear'] = []
+                for s_apid in self.sub_grb_obj.apertures:
+                    for s_el in self.sub_grb_obj.apertures[s_apid]['geometry']:
+                        if "solid" in s_el:
+                            sub_geometry['solid'].append(s_el["solid"])
+                        if "clear" in s_el:
+                            sub_geometry['clear'].append(s_el["clear"])
 
 
-        # start the QTimer to check for promises with 0.5 second period check
-        self.periodic_check(500, reset=True)
+                self.results.append(
+                    self.pool.apply_async(self.aperture_intersection, args=(apid, target_geo, sub_geometry))
+                )
 
 
-        for apid in self.target_grb_obj.apertures:
-            geo = self.target_grb_obj.apertures[apid]['geometry']
-            self.app.worker_task.emit({'fcn': self.aperture_intersection, 'params': [apid, geo]})
+            output = []
+            for p in self.results:
+                res = p.get()
+                output.append(res)
+                app_obj.inform.emit('%s: %s...' % (_("Finished parsing geometry for aperture"), str(res[0])))
 
 
-    def aperture_intersection(self, apid, geo):
-        new_geometry = []
+            app_obj.inform.emit("%s" % _("Subtraction aperture processing finished."))
 
 
-        log.debug("Working on promise: %s" % str(apid))
+            outname = self.target_gerber_combo.currentText() + '_sub'
+            self.aperture_processing_finished.emit(outname, output)
 
 
-        with self.app.proc_container.new('%s: %s...' % (_("Parsing geometry for aperture"), str(apid))):
+        self.app.worker_task.emit({'fcn': worker_job, 'params': [self.app]})
 
 
-            for geo_el in geo:
-                new_el = {}
+    @staticmethod
+    def aperture_intersection(apid, target_geo, sub_geometry):
+        """
 
 
-                if 'solid' in geo_el:
-                    work_geo = geo_el['solid']
-                    if self.sub_solid_union:
-                        if work_geo.intersects(self.sub_solid_union):
-                            new_geo = work_geo.difference(self.sub_solid_union)
-                            new_geo = new_geo.buffer(0)
-                            if new_geo:
-                                if not new_geo.is_empty:
-                                    new_el['solid'] = new_geo
-                                else:
-                                    new_el['solid'] = work_geo
-                            else:
-                                new_el['solid'] = work_geo
-                        else:
-                            new_el['solid'] = work_geo
-                    else:
-                        new_el['solid'] = work_geo
-
-                if 'follow' in geo_el:
-                    work_geo = geo_el['follow']
-                    if self.sub_follow_union:
-                        if work_geo.intersects(self.sub_follow_union):
-                            new_geo = work_geo.difference(self.sub_follow_union)
-                            new_geo = new_geo.buffer(0)
-                            if new_geo:
-                                if not new_geo.is_empty:
-                                    new_el['follow'] = new_geo
-                                else:
-                                    new_el['follow'] = work_geo
-                            else:
-                                new_el['follow'] = work_geo
-                        else:
-                            new_el['follow'] = work_geo
-                    else:
-                        new_el['follow'] = work_geo
-
-                if 'clear' in geo_el:
-                    work_geo = geo_el['clear']
-                    if self.sub_clear_union:
-                        if work_geo.intersects(self.sub_clear_union):
-                            new_geo = work_geo.difference(self.sub_clear_union)
-                            new_geo = new_geo.buffer(0)
-                            if new_geo:
-                                if not new_geo.is_empty:
-                                    new_el['clear'] = new_geo
-                                else:
-                                    new_el['clear'] = work_geo
-                            else:
-                                new_el['clear'] = work_geo
-                        else:
-                            new_el['clear'] = work_geo
-                    else:
-                        new_el['clear'] = work_geo
+        :param apid:            the aperture id for which we process geometry
+        :type apid:             str
+        :param target_geo:      the geometry list that holds the geometry from which we subtract
+        :type target_geo:       list
+        :param sub_geometry:    the apertures dict that holds all the geometry that is subtracted
+        :type sub_geometry:     dict
+        :return:                (apid, unaffected_geometry lsit, affected_geometry list)
+        :rtype:                 tuple
+        """
 
 
-                new_geometry.append(deepcopy(new_el))
+        unafected_geo = []
+        affected_geo = []
+
+        is_modified = False
+        for geo_el in target_geo:
+            new_geo_el = {}
+            if "solid" in geo_el:
+                for sub_solid_geo in sub_geometry["solid"]:
+                    if geo_el["solid"].intersects(sub_solid_geo):
+                        new_geo = geo_el["solid"].difference(sub_solid_geo)
+                        if not new_geo.is_empty:
+                            geo_el["solid"] = new_geo
+                            is_modified = True
+
+                new_geo_el["solid"] = deepcopy(geo_el["solid"])
+
+            if "clear" in geo_el:
+                for sub_solid_geo in sub_geometry["clear"]:
+                    if geo_el["clear"].intersects(sub_solid_geo):
+                        new_geo = geo_el["clear"].difference(sub_solid_geo)
+                        if not new_geo.is_empty:
+                            geo_el["clear"] = new_geo
+                            is_modified = True
+
+                new_geo_el["clear"] = deepcopy(geo_el["clear"])
+
+            if is_modified:
+                affected_geo.append(new_geo_el)
+            else:
+                unafected_geo.append(geo_el)
 
 
-        self.app.inform.emit('%s: %s...' % (_("Finished parsing geometry for aperture"), str(apid)))
+        return apid, unafected_geo, affected_geo
 
 
-        if new_geometry:
-            while not self.new_apertures[apid]['geometry']:
-                self.new_apertures[apid]['geometry'] = deepcopy(new_geometry)
-                time.sleep(0.5)
+    def new_gerber_object(self, outname, output):
+        """
 
 
-        while True:
-            # removal from list is done in a multithreaded way therefore not always the removal can be done
-            # so we keep trying until it's done
-            if apid not in self.promises:
-                break
+        :param outname:     name for the new Gerber object
+        :type outname:      str
+        :param output:      a list made of tuples in format:
+                            (aperture id in the target Gerber, unaffected_geometry list, affected_geometry list)
+        :type output:       list
+        :return:
+        :rtype:
+        """
 
 
-            self.promises.remove(apid)
-            time.sleep(0.5)
+        def obj_init(grb_obj, app_obj):
 
 
-        log.debug("Promise fulfilled: %s" % str(apid))
+            grb_obj.apertures = deepcopy(self.new_apertures)
 
 
-    def new_gerber_object(self, outname):
+            if '0' not in grb_obj.apertures:
+                grb_obj.apertures['0'] = {}
+                grb_obj.apertures['0']['type'] = 'REG'
+                grb_obj.apertures['0']['size'] = 0.0
+                grb_obj.apertures['0']['geometry'] = []
+
+            for apid, apid_val in list(grb_obj.apertures.items()):
+                for t in output:
+                    new_apid = t[0]
+                    if apid == new_apid:
+                        surving_geo = t[1]
+                        modified_geo = t[2]
+                        if surving_geo:
+                            apid_val['geometry'] = deepcopy(surving_geo)
+                        else:
+                            grb_obj.apertures.pop(apid, None)
 
 
-        def obj_init(grb_obj, app_obj):
+                        if modified_geo:
+                            grb_obj.apertures['0']['geometry'] += modified_geo
 
 
-            grb_obj.apertures = deepcopy(self.new_apertures)
+            # delete the '0' aperture if it has no geometry
+            if not grb_obj.apertures['0']['geometry']:
+                grb_obj.apertures.pop('0', None)
 
 
             poly_buff = []
             poly_buff = []
             follow_buff = []
             follow_buff = []
-            for ap in self.new_apertures:
-                for elem in self.new_apertures[ap]['geometry']:
-                    poly_buff.append(elem['solid'])
-                    follow_buff.append(elem['follow'])
+            for ap in grb_obj.apertures:
+                for elem in grb_obj.apertures[ap]['geometry']:
+                    if 'solid' in elem:
+                        solid_geo = elem['solid']
+                        poly_buff.append(solid_geo)
+                    if 'follow' in elem:
+                        follow_buff.append(elem['follow'])
 
 
-            work_poly_buff = cascaded_union(poly_buff)
+            work_poly_buff = MultiPolygon(poly_buff)
             try:
             try:
                 poly_buff = work_poly_buff.buffer(0.0000001)
                 poly_buff = work_poly_buff.buffer(0.0000001)
             except ValueError:
             except ValueError:
@@ -454,25 +458,22 @@ class ToolSub(AppTool):
 
 
             grb_obj.solid_geometry = deepcopy(poly_buff)
             grb_obj.solid_geometry = deepcopy(poly_buff)
             grb_obj.follow_geometry = deepcopy(follow_buff)
             grb_obj.follow_geometry = deepcopy(follow_buff)
+            grb_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
+                                                         local_use=grb_obj, use_thread=False)
 
 
         with self.app.proc_container.new(_("Generating new object ...")):
         with self.app.proc_container.new(_("Generating new object ...")):
             ret = self.app.app_obj.new_object('gerber', outname, obj_init, autoselected=False)
             ret = self.app.app_obj.new_object('gerber', outname, obj_init, autoselected=False)
             if ret == 'fail':
             if ret == 'fail':
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _('Generating new object failed.'))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _('Generating new object failed.'))
                 return
                 return
 
 
             # GUI feedback
             # GUI feedback
-            self.app.inform.emit('[success] %s: %s' %
-                                 (_("Created"), outname))
+            self.app.inform.emit('[success] %s: %s' % (_("Created"), outname))
 
 
             # cleanup
             # cleanup
             self.new_apertures.clear()
             self.new_apertures.clear()
             self.new_solid_geometry[:] = []
             self.new_solid_geometry[:] = []
-            try:
-                self.sub_union[:] = []
-            except TypeError:
-                self.sub_union = []
+            self.results = []
 
 
     def on_geo_intersection_click(self):
     def on_geo_intersection_click(self):
         # reset previous values
         # reset previous values
@@ -739,11 +740,9 @@ class ToolSub(AppTool):
                 outname = self.target_geo_combo.currentText() + '_sub'
                 outname = self.target_geo_combo.currentText() + '_sub'
 
 
                 # intersection jobs finished, start the creation of solid_geometry
                 # intersection jobs finished, start the creation of solid_geometry
-                self.app.worker_task.emit({'fcn': self.new_geo_object,
-                                           'params': [outname]})
+                self.app.worker_task.emit({'fcn': self.new_geo_object, 'params': [outname]})
         else:
         else:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _('Generating new object failed.'))
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _('Generating new object failed.'))
 
 
     def reset_fields(self):
     def reset_fields(self):
         self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 4 - 0
AppTranslation.py

@@ -106,6 +106,8 @@ def on_language_apply_click(app, restart=False):
                                   (_("Are you sure do you want to change the current language to"), name.capitalize()))
                                   (_("Are you sure do you want to change the current language to"), name.capitalize()))
         msgbox.setWindowTitle(_("Apply Language ..."))
         msgbox.setWindowTitle(_("Apply Language ..."))
         msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/language32.png'))
         msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/language32.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         bt_yes = msgbox.addButton(_("Yes"), QtWidgets.QMessageBox.YesRole)
         bt_yes = msgbox.addButton(_("Yes"), QtWidgets.QMessageBox.YesRole)
         bt_no = msgbox.addButton(_("No"), QtWidgets.QMessageBox.NoRole)
         bt_no = msgbox.addButton(_("No"), QtWidgets.QMessageBox.NoRole)
 
 
@@ -203,6 +205,8 @@ def restart_program(app, ask=None):
                          "Do you want to Save the project?"))
                          "Do you want to Save the project?"))
         msgbox.setWindowTitle(_("Save changes"))
         msgbox.setWindowTitle(_("Save changes"))
         msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/save_as.png'))
         msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/save_as.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
         bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
         bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
         bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
 
 

+ 131 - 162
App_Main.py

@@ -204,7 +204,7 @@ class App(QtCore.QObject):
     # Inform the user
     # Inform the user
     # Handled by:
     # Handled by:
     #  * App.info() --> Print on the status bar
     #  * App.info() --> Print on the status bar
-    inform = QtCore.pyqtSignal(str)
+    inform = QtCore.pyqtSignal([str], [str, bool])
 
 
     app_quit = QtCore.pyqtSignal()
     app_quit = QtCore.pyqtSignal()
 
 
@@ -757,7 +757,9 @@ class App(QtCore.QObject):
 
 
         # ########################################## Custom signals  ################################################
         # ########################################## Custom signals  ################################################
         # signal for displaying messages in status bar
         # signal for displaying messages in status bar
-        self.inform.connect(self.info)
+        self.inform[str].connect(self.info)
+        self.inform[str, bool].connect(self.info)
+
         # signal to be called when the app is quiting
         # signal to be called when the app is quiting
         self.app_quit.connect(self.quit_application, type=Qt.QueuedConnection)
         self.app_quit.connect(self.quit_application, type=Qt.QueuedConnection)
         self.message.connect(lambda: message_dialog(parent=self.ui))
         self.message.connect(lambda: message_dialog(parent=self.ui))
@@ -871,10 +873,11 @@ class App(QtCore.QObject):
         self.ui.menuview_toggle_notebook.triggered.connect(self.ui.on_toggle_notebook)
         self.ui.menuview_toggle_notebook.triggered.connect(self.ui.on_toggle_notebook)
         self.ui.menu_toggle_nb.triggered.connect(self.ui.on_toggle_notebook)
         self.ui.menu_toggle_nb.triggered.connect(self.ui.on_toggle_notebook)
         self.ui.menuview_toggle_grid.triggered.connect(self.ui.on_toggle_grid)
         self.ui.menuview_toggle_grid.triggered.connect(self.ui.on_toggle_grid)
-        self.ui.menuview_toggle_grid_lines.triggered.connect(self.on_toggle_grid_lines)
-        self.ui.menuview_toggle_axis.triggered.connect(self.on_toggle_axis)
         self.ui.menuview_toggle_workspace.triggered.connect(self.on_workspace_toggle)
         self.ui.menuview_toggle_workspace.triggered.connect(self.on_workspace_toggle)
-        self.ui.menuview_toggle_hud.triggered.connect(self.on_toggle_hud)
+
+        self.ui.menuview_toggle_grid_lines.triggered.connect(self.plotcanvas.on_toggle_grid_lines)
+        self.ui.menuview_toggle_axis.triggered.connect(self.plotcanvas.on_toggle_axis)
+        self.ui.menuview_toggle_hud.triggered.connect(self.plotcanvas.on_toggle_hud)
 
 
         self.ui.menutoolshell.triggered.connect(self.ui.toggle_shell_ui)
         self.ui.menutoolshell.triggered.connect(self.ui.toggle_shell_ui)
 
 
@@ -1019,6 +1022,15 @@ class App(QtCore.QObject):
         self.ui.util_defaults_form.kw_group.del_btn.clicked.connect(
         self.ui.util_defaults_form.kw_group.del_btn.clicked.connect(
             lambda: self.del_extension(ext_type='keyword'))
             lambda: self.del_extension(ext_type='keyword'))
 
 
+        # ###########################################################################################################
+        # ########################################### GUI SIGNALS ###################################################
+        # ###########################################################################################################
+        self.ui.hud_label.clicked.connect(self.plotcanvas.on_toggle_hud)
+        self.ui.axis_status_label.clicked.connect(self.plotcanvas.on_toggle_axis)
+
+        # ###########################################################################################################
+        # ####################################### VARIOUS SIGNALS ###################################################
+        # ###########################################################################################################
         # connect the abort_all_tasks related slots to the related signals
         # connect the abort_all_tasks related slots to the related signals
         self.proc_container.idle_flag.connect(self.app_is_idle)
         self.proc_container.idle_flag.connect(self.app_is_idle)
 
 
@@ -1343,7 +1355,7 @@ class App(QtCore.QObject):
         try:
         try:
             self.install_tools()
             self.install_tools()
         except AttributeError as e:
         except AttributeError as e:
-            log.debug("App.__init__() install tools() --> %s" % str(e))
+            log.debug("App.__init__() install_tools() --> %s" % str(e))
 
 
         # ###########################################################################################################
         # ###########################################################################################################
         # ############################################ SETUP RECENT ITEMS ###########################################
         # ############################################ SETUP RECENT ITEMS ###########################################
@@ -1439,12 +1451,6 @@ class App(QtCore.QObject):
         # holds the key modifier if pressed (CTRL, SHIFT or ALT)
         # holds the key modifier if pressed (CTRL, SHIFT or ALT)
         self.key_modifiers = None
         self.key_modifiers = None
 
 
-        # Variable to hold the status of the axis
-        self.toggle_axis = True
-
-        # Variable to hold the status of the grid lines
-        self.toggle_grid_lines = True
-
         # Variable to store the status of the code editor
         # Variable to store the status of the code editor
         self.toggle_codeeditor = False
         self.toggle_codeeditor = False
 
 
@@ -2206,6 +2212,7 @@ class App(QtCore.QObject):
                 msgbox.setText(_("Do you want to save the edited object?"))
                 msgbox.setText(_("Do you want to save the edited object?"))
                 msgbox.setWindowTitle(_("Close Editor"))
                 msgbox.setWindowTitle(_("Close Editor"))
                 msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png'))
                 msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png'))
+                msgbox.setIcon(QtWidgets.QMessageBox.Question)
 
 
                 bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
                 bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
                 bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
                 bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
@@ -2242,6 +2249,7 @@ class App(QtCore.QObject):
                             log.debug("App.editor2object() --> Geometry --> %s" % str(e))
                             log.debug("App.editor2object() --> Geometry --> %s" % str(e))
 
 
                         edited_obj.build_ui()
                         edited_obj.build_ui()
+                        edited_obj.plot()
                         self.inform.emit('[success] %s' % _("Editor exited. Editor content saved."))
                         self.inform.emit('[success] %s' % _("Editor exited. Editor content saved."))
 
 
                     elif isinstance(edited_obj, GerberObject):
                     elif isinstance(edited_obj, GerberObject):
@@ -2298,6 +2306,7 @@ class App(QtCore.QObject):
                     if isinstance(edited_obj, GeometryObject):
                     if isinstance(edited_obj, GeometryObject):
                         self.geo_editor.deactivate()
                         self.geo_editor.deactivate()
                         edited_obj.build_ui()
                         edited_obj.build_ui()
+                        edited_obj.plot()
                     elif isinstance(edited_obj, GerberObject):
                     elif isinstance(edited_obj, GerberObject):
                         self.grb_editor.deactivate_grb_editor()
                         self.grb_editor.deactivate_grb_editor()
                         edited_obj.build_ui()
                         edited_obj.build_ui()
@@ -2361,12 +2370,17 @@ class App(QtCore.QObject):
             loc = os.path.dirname(__file__)
             loc = os.path.dirname(__file__)
         return loc
         return loc
 
 
-    def info(self, msg):
+    @QtCore.pyqtSlot(str)
+    @QtCore.pyqtSlot(str, bool)
+    def info(self, msg, shell_echo=True):
         """
         """
         Informs the user. Normally on the status bar, optionally
         Informs the user. Normally on the status bar, optionally
         also on the shell.
         also on the shell.
 
 
-        :param msg: Text to write.
+        :param msg:         Text to write.
+        :type msg:          str
+        :param shell_echo:  Control if to display the message msg in the Shell
+        :type shell_echo:   bool
         :return: None
         :return: None
         """
         """
 
 
@@ -2377,32 +2391,33 @@ class App(QtCore.QObject):
             msg_ = match.group(2)
             msg_ = match.group(2)
             self.ui.fcinfo.set_status(str(msg_), level=level)
             self.ui.fcinfo.set_status(str(msg_), level=level)
 
 
-            if level.lower() == "error":
-                self.shell_message(msg, error=True, show=True)
-            elif level.lower() == "warning":
-                self.shell_message(msg, warning=True, show=True)
+            if shell_echo is True:
+                if level.lower() == "error":
+                    self.shell_message(msg, error=True, show=True)
+                elif level.lower() == "warning":
+                    self.shell_message(msg, warning=True, show=True)
 
 
-            elif level.lower() == "error_notcl":
-                self.shell_message(msg, error=True, show=False)
+                elif level.lower() == "error_notcl":
+                    self.shell_message(msg, error=True, show=False)
 
 
-            elif level.lower() == "warning_notcl":
-                self.shell_message(msg, warning=True, show=False)
+                elif level.lower() == "warning_notcl":
+                    self.shell_message(msg, warning=True, show=False)
 
 
-            elif level.lower() == "success":
-                self.shell_message(msg, success=True, show=False)
+                elif level.lower() == "success":
+                    self.shell_message(msg, success=True, show=False)
 
 
-            elif level.lower() == "selected":
-                self.shell_message(msg, selected=True, show=False)
+                elif level.lower() == "selected":
+                    self.shell_message(msg, selected=True, show=False)
 
 
-            else:
-                self.shell_message(msg, show=False)
+                else:
+                    self.shell_message(msg, show=False)
 
 
         else:
         else:
             self.ui.fcinfo.set_status(str(msg), level="info")
             self.ui.fcinfo.set_status(str(msg), level="info")
 
 
             # make sure that if the message is to clear the infobar with a space
             # make sure that if the message is to clear the infobar with a space
             # is not printed over and over on the shell
             # is not printed over and over on the shell
-            if msg != '':
+            if msg != '' and shell_echo is True:
                 self.shell_message(msg)
                 self.shell_message(msg)
 
 
     def on_import_preferences(self):
     def on_import_preferences(self):
@@ -2456,10 +2471,11 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export FlatCAM Preferences"),
                 caption=_("Export FlatCAM Preferences"),
                 directory=self.data_path + '/preferences_' + date,
                 directory=self.data_path + '/preferences_' + date,
-                filter=filter__
+                ext_filter=filter__
             )
             )
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export FlatCAM Preferences"), filter=filter__)
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export FlatCAM Preferences"), ext_filter=filter__)
         filename = str(filename)
         filename = str(filename)
         if filename == "":
         if filename == "":
             self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
             self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
@@ -2501,10 +2517,10 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Save to file"),
                 caption=_("Save to file"),
                 directory=path_to_save + '/file_' + self.date,
                 directory=path_to_save + '/file_' + self.date,
-                filter=filter__
+                ext_filter=filter__
             )
             )
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save to file"), filter=filter__)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save to file"), ext_filter=filter__)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -3092,6 +3108,8 @@ class App(QtCore.QObject):
 
 
         msgbox.setWindowTitle(_("Alternative website"))
         msgbox.setWindowTitle(_("Alternative website"))
         msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/globe16.png'))
         msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/globe16.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         bt_yes = msgbox.addButton(_('Close'), QtWidgets.QMessageBox.YesRole)
         bt_yes = msgbox.addButton(_('Close'), QtWidgets.QMessageBox.YesRole)
 
 
         msgbox.setDefaultButton(bt_yes)
         msgbox.setDefaultButton(bt_yes)
@@ -3855,9 +3873,10 @@ class App(QtCore.QObject):
             return
             return
 
 
         # Keys in self.defaults for which to scale their values
         # Keys in self.defaults for which to scale their values
-        dimensions = ['gerber_isotooldia', 'gerber_noncoppermargin', 'gerber_bboxmargin',
-                      "gerber_editor_newsize", "gerber_editor_lin_pitch", "gerber_editor_buff_f", "gerber_vtipdia",
-                      "gerber_vcutz", "gerber_editor_newdim", "gerber_editor_ma_low",
+        dimensions = ['tools_iso_tooldia', 'gerber_noncoppermargin', 'gerber_bboxmargin',
+                      "gerber_editor_newsize", "gerber_editor_lin_pitch", "gerber_editor_buff_f",
+                      "tools_iso_tool_vtipdia",
+                      "tools_iso_tool_cutz", "gerber_editor_newdim", "gerber_editor_ma_low",
                       "gerber_editor_ma_high",
                       "gerber_editor_ma_high",
 
 
                       'excellon_cutz', 'excellon_travelz', "excellon_toolchangexy", 'excellon_offset',
                       'excellon_cutz', 'excellon_travelz', "excellon_toolchangexy", 'excellon_offset',
@@ -4000,6 +4019,8 @@ class App(QtCore.QObject):
         msgbox = QtWidgets.QMessageBox()
         msgbox = QtWidgets.QMessageBox()
         msgbox.setWindowTitle(_("Toggle Units"))
         msgbox.setWindowTitle(_("Toggle Units"))
         msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/toggle_units32.png'))
         msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/toggle_units32.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         msgbox.setText(_("Changing the units of the project\n"
         msgbox.setText(_("Changing the units of the project\n"
                          "will scale all objects.\n\n"
                          "will scale all objects.\n\n"
                          "Do you want to continue?"))
                          "Do you want to continue?"))
@@ -4079,95 +4100,6 @@ class App(QtCore.QObject):
         self.ui.grid_gap_x_entry.set_value(val_x, decimals=self.decimals)
         self.ui.grid_gap_x_entry.set_value(val_x, decimals=self.decimals)
         self.ui.grid_gap_y_entry.set_value(val_y, decimals=self.decimals)
         self.ui.grid_gap_y_entry.set_value(val_y, decimals=self.decimals)
 
 
-    def on_toggle_axis(self):
-        self.defaults.report_usage("on_toggle_axis()")
-
-        if self.toggle_axis is False:
-            if self.is_legacy is False:
-                self.plotcanvas.v_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=True,
-                                                      parent=self.plotcanvas.view.scene)
-
-                self.plotcanvas.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=False,
-                                                      parent=self.plotcanvas.view.scene)
-            else:
-                if self.plotcanvas.h_line not in self.plotcanvas.axes.lines and \
-                        self.plotcanvas.v_line not in self.plotcanvas.axes.lines:
-                    self.plotcanvas.h_line = self.plotcanvas.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
-                    self.plotcanvas.v_line = self.plotcanvas.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
-                    self.plotcanvas.canvas.draw()
-            self.inform.emit(_("Axis enabled."))
-            self.toggle_axis = True
-        else:
-            if self.is_legacy is False:
-                self.plotcanvas.v_line.parent = None
-                self.plotcanvas.h_line.parent = None
-            else:
-                if self.plotcanvas.h_line in self.plotcanvas.axes.lines and \
-                        self.plotcanvas.v_line in self.plotcanvas.axes.lines:
-                    self.plotcanvas.axes.lines.remove(self.plotcanvas.h_line)
-                    self.plotcanvas.axes.lines.remove(self.plotcanvas.v_line)
-                    self.plotcanvas.canvas.draw()
-            self.inform.emit(_("Axis disabled."))
-            self.toggle_axis = False
-
-    def on_toggle_hud(self):
-        new_state = False if self.plotcanvas.hud_enabled else True
-
-        self.plotcanvas.on_toggle_hud(state=new_state)
-        if new_state is False:
-            self.inform.emit(_("HUD disabled."))
-        else:
-            self.inform.emit(_("HUD enabled."))
-
-    def on_toggle_grid_lines(self):
-        self.defaults.report_usage("on_toggle_grd_lines()")
-
-        tt_settings = QtCore.QSettings("Open Source", "FlatCAM")
-        if tt_settings.contains("theme"):
-            theme = tt_settings.value('theme', type=str)
-        else:
-            theme = 'white'
-
-        if self.toggle_grid_lines is False:
-            if self.is_legacy is False:
-                if theme == 'white':
-                    self.plotcanvas.grid._grid_color_fn['color'] = Color('dimgray').rgba
-                else:
-                    self.plotcanvas.grid._grid_color_fn['color'] = Color('#dededeff').rgba
-            else:
-                self.plotcanvas.axes.grid(True)
-                try:
-                    self.plotcanvas.canvas.draw()
-                except IndexError:
-                    pass
-                pass
-            self.inform.emit(_("Grid enabled."))
-            self.toggle_grid_lines = True
-        else:
-            if self.is_legacy is False:
-                if theme == 'white':
-                    self.plotcanvas.grid._grid_color_fn['color'] = Color('#ffffffff').rgba
-                else:
-                    self.plotcanvas.grid._grid_color_fn['color'] = Color('#000000FF').rgba
-            else:
-                self.plotcanvas.axes.grid(False)
-                try:
-                    self.plotcanvas.canvas.draw()
-                except IndexError:
-                    pass
-            self.toggle_grid_lines = False
-            self.inform.emit(_("Grid disabled."))
-
-        if self.is_legacy is False:
-            # HACK: enabling/disabling the cursor seams to somehow update the shapes on screen
-            # - perhaps is a bug in VisPy implementation
-            if self.grid_status():
-                self.app_cursor.enabled = False
-                self.app_cursor.enabled = True
-            else:
-                self.app_cursor.enabled = True
-                self.app_cursor.enabled = False
-
     def on_tab_rmb_click(self, checked):
     def on_tab_rmb_click(self, checked):
         self.ui.notebook.set_detachable(val=checked)
         self.ui.notebook.set_detachable(val=checked)
         self.defaults["global_tabs_detachable"] = checked
         self.defaults["global_tabs_detachable"] = checked
@@ -4206,10 +4138,10 @@ class App(QtCore.QObject):
     def on_workspace(self):
     def on_workspace(self):
         if self.ui.general_defaults_form.general_app_set_group.workspace_cb.get_value():
         if self.ui.general_defaults_form.general_app_set_group.workspace_cb.get_value():
             self.plotcanvas.draw_workspace(workspace_size=self.defaults['global_workspaceT'])
             self.plotcanvas.draw_workspace(workspace_size=self.defaults['global_workspaceT'])
-            self.inform.emit(_("Workspace enabled."))
+            self.inform[str, bool].emit(_("Workspace enabled."), False)
         else:
         else:
             self.plotcanvas.delete_workspace()
             self.plotcanvas.delete_workspace()
-            self.inform.emit(_("Workspace disabled."))
+            self.inform[str, bool].emit(_("Workspace disabled."), False)
         self.preferencesUiManager.defaults_read_form()
         self.preferencesUiManager.defaults_read_form()
         # self.save_defaults(silent=True)
         # self.save_defaults(silent=True)
 
 
@@ -4277,6 +4209,8 @@ class App(QtCore.QObject):
                                      "Go to Preferences -> General - Show Advanced Options."))
                                      "Go to Preferences -> General - Show Advanced Options."))
                     msgbox.setWindowTitle("Tool adding ...")
                     msgbox.setWindowTitle("Tool adding ...")
                     msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/warning.png'))
                     msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/warning.png'))
+                    msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
                     bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
                     bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
 
 
                     msgbox.setDefaultButton(bt_ok)
                     msgbox.setDefaultButton(bt_ok)
@@ -4323,6 +4257,10 @@ class App(QtCore.QObject):
             # and only if the tool is Solder Paste Dispensing Tool
             # and only if the tool is Solder Paste Dispensing Tool
             elif tool_widget == self.paste_tool.toolName:
             elif tool_widget == self.paste_tool.toolName:
                 self.paste_tool.on_tool_delete()
                 self.paste_tool.on_tool_delete()
+
+            # and only if the tool is Isolation Tool
+            elif tool_widget == self.isolation_tool.toolName:
+                self.isolation_tool.on_tool_delete()
         else:
         else:
             self.on_delete()
             self.on_delete()
 
 
@@ -4351,6 +4289,8 @@ class App(QtCore.QObject):
                 msgbox = QtWidgets.QMessageBox()
                 msgbox = QtWidgets.QMessageBox()
                 msgbox.setWindowTitle(_("Delete objects"))
                 msgbox.setWindowTitle(_("Delete objects"))
                 msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/deleteshape32.png'))
                 msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/deleteshape32.png'))
+                msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
                 # msgbox.setText("<B>%s</B>" % _("Change project units ..."))
                 # msgbox.setText("<B>%s</B>" % _("Change project units ..."))
                 msgbox.setText(_("Are you sure you want to permanently delete\n"
                 msgbox.setText(_("Are you sure you want to permanently delete\n"
                                  "the selected objects?"))
                                  "the selected objects?"))
@@ -4376,7 +4316,7 @@ class App(QtCore.QObject):
                                 obj_active.mark_shapes[el].enabled = False
                                 obj_active.mark_shapes[el].enabled = False
                                 # obj_active.mark_shapes[el] = None
                                 # obj_active.mark_shapes[el] = None
                                 del el
                                 del el
-                        elif isinstance(obj_active, CNCJobObject):
+                        elif obj_active.kind == 'cncjob':
                             try:
                             try:
                                 obj_active.text_col.enabled = False
                                 obj_active.text_col.enabled = False
                                 del obj_active.text_col
                                 del obj_active.text_col
@@ -4390,13 +4330,13 @@ class App(QtCore.QObject):
                     while self.collection.get_selected():
                     while self.collection.get_selected():
                         self.delete_first_selected()
                         self.delete_first_selected()
 
 
-                    self.inform.emit('%s...' % _("Object(s) deleted"))
                     # make sure that the selection shape is deleted, too
                     # make sure that the selection shape is deleted, too
                     self.delete_selection_shape()
                     self.delete_selection_shape()
 
 
                     # if there are no longer objects delete also the exclusion areas shapes
                     # if there are no longer objects delete also the exclusion areas shapes
                     if not self.collection.get_list():
                     if not self.collection.get_list():
                         self.exc_areas.clear_shapes()
                         self.exc_areas.clear_shapes()
+                    self.inform.emit('%s...' % _("Object(s) deleted"))
                 else:
                 else:
                     self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No object(s) selected..."))
                     self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No object(s) selected..."))
         else:
         else:
@@ -5283,7 +5223,13 @@ class App(QtCore.QObject):
                 callback_on_edited=self.on_tools_db_edited,
                 callback_on_edited=self.on_tools_db_edited,
                 callback_on_tool_request=self.paint_tool.on_paint_tool_add_from_db_executed
                 callback_on_tool_request=self.paint_tool.on_paint_tool_add_from_db_executed
             )
             )
-
+        elif source == 'iso':
+            self.tools_db_tab = ToolsDB2(
+                app=self,
+                parent=self.ui,
+                callback_on_edited=self.on_tools_db_edited,
+                callback_on_tool_request=self.isolation_tool.on_iso_tool_add_from_db_executed
+            )
         # add the tab if it was closed
         # add the tab if it was closed
         try:
         try:
             self.ui.plot_tab_area.addTab(self.tools_db_tab, _("Tools Database"))
             self.ui.plot_tab_area.addTab(self.tools_db_tab, _("Tools Database"))
@@ -5310,7 +5256,7 @@ class App(QtCore.QObject):
         :return:
         :return:
         """
         """
 
 
-        self.inform.emit('[WARNING_NOTCL] %s' % _("Tools in Tools Database edited but not saved."))
+        self.inform[str, bool].emit('[WARNING_NOTCL] %s' % _("Tools in Tools Database edited but not saved."), False)
 
 
         for idx in range(self.ui.plot_tab_area.count()):
         for idx in range(self.ui.plot_tab_area.count()):
             if self.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
             if self.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
@@ -5328,9 +5274,19 @@ class App(QtCore.QObject):
         tool_from_db = deepcopy(tool)
         tool_from_db = deepcopy(tool)
 
 
         obj = self.collection.get_active()
         obj = self.collection.get_active()
-        if isinstance(obj, GeometryObject):
+        if obj.kind == 'geometry':
             obj.on_tool_from_db_inserted(tool=tool_from_db)
             obj.on_tool_from_db_inserted(tool=tool_from_db)
 
 
+            # close the tab and delete it
+            for idx in range(self.ui.plot_tab_area.count()):
+                if self.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                    wdg = self.ui.plot_tab_area.widget(idx)
+                    wdg.deleteLater()
+                    self.ui.plot_tab_area.removeTab(idx)
+            self.inform.emit('[success] %s' % _("Tool from DB added in Tool Table."))
+        elif obj.kind == 'gerber':
+            self.isolation_tool.on_tool_from_db_inserted(tool=tool_from_db)
+
             # close the tab and delete it
             # close the tab and delete it
             for idx in range(self.ui.plot_tab_area.count()):
             for idx in range(self.ui.plot_tab_area.count()):
                 if self.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
                 if self.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
@@ -5361,6 +5317,7 @@ class App(QtCore.QObject):
                                  "Do you want to update the Tools Database?"))
                                  "Do you want to update the Tools Database?"))
                 msgbox.setWindowTitle(_("Save Tools Database"))
                 msgbox.setWindowTitle(_("Save Tools Database"))
                 msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png'))
                 msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png'))
+                msgbox.setIcon(QtWidgets.QMessageBox.Question)
 
 
                 bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
                 bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
                 msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
                 msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
@@ -5643,7 +5600,6 @@ class App(QtCore.QObject):
         :return: None
         :return: None
         """
         """
 
 
-        self.defaults.report_usage("on_toolbar_replot")
         self.log.debug("on_toolbar_replot()")
         self.log.debug("on_toolbar_replot()")
 
 
         try:
         try:
@@ -6422,6 +6378,8 @@ class App(QtCore.QObject):
                              "Do you want to Save the project?"))
                              "Do you want to Save the project?"))
             msgbox.setWindowTitle(_("Save changes"))
             msgbox.setWindowTitle(_("Save changes"))
             msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png'))
             msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png'))
+            msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
             bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
             bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
             bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
             bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
             bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.RejectRole)
             bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.RejectRole)
@@ -6807,6 +6765,8 @@ class App(QtCore.QObject):
             self.inform.emit('[WARNING_NOTCL] %s' % _("No object selected."))
             self.inform.emit('[WARNING_NOTCL] %s' % _("No object selected."))
             msg = _("Please Select a Geometry object to export")
             msg = _("Please Select a Geometry object to export")
             msgbox = QtWidgets.QMessageBox()
             msgbox = QtWidgets.QMessageBox()
+            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
             msgbox.setInformativeText(msg)
             msgbox.setInformativeText(msg)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             msgbox.setDefaultButton(bt_ok)
             msgbox.setDefaultButton(bt_ok)
@@ -6821,6 +6781,8 @@ class App(QtCore.QObject):
             msg = '[ERROR_NOTCL] %s' % \
             msg = '[ERROR_NOTCL] %s' % \
                   _("Only Geometry, Gerber and CNCJob objects can be used.")
                   _("Only Geometry, Gerber and CNCJob objects can be used.")
             msgbox = QtWidgets.QMessageBox()
             msgbox = QtWidgets.QMessageBox()
+            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
             msgbox.setInformativeText(msg)
             msgbox.setInformativeText(msg)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             msgbox.setDefaultButton(bt_ok)
             msgbox.setDefaultButton(bt_ok)
@@ -6834,9 +6796,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export SVG"),
                 caption=_("Export SVG"),
                 directory=self.get_last_save_folder() + '/' + str(name) + '_svg',
                 directory=self.get_last_save_folder() + '/' + str(name) + '_svg',
-                filter=_filter)
+                ext_filter=_filter)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export SVG"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export SVG"), ext_filter=_filter)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -6857,8 +6819,9 @@ class App(QtCore.QObject):
         self.date = ''.join(c for c in self.date if c not in ':-')
         self.date = ''.join(c for c in self.date if c not in ':-')
         self.date = self.date.replace(' ', '_')
         self.date = self.date.replace(' ', '_')
 
 
+        data = None
         if self.is_legacy is False:
         if self.is_legacy is False:
-            image = _screenshot()
+            image = _screenshot(alpha=None)
             data = np.asarray(image)
             data = np.asarray(image)
             if not data.ndim == 3 and data.shape[-1] in (3, 4):
             if not data.ndim == 3 and data.shape[-1] in (3, 4):
                 self.inform.emit('[[WARNING_NOTCL]] %s' % _('Data must be a 3D array with last dimension 3 or 4'))
                 self.inform.emit('[[WARNING_NOTCL]] %s' % _('Data must be a 3D array with last dimension 3 or 4'))
@@ -6869,9 +6832,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export PNG Image"),
                 caption=_("Export PNG Image"),
                 directory=self.get_last_save_folder() + '/png_' + self.date,
                 directory=self.get_last_save_folder() + '/png_' + self.date,
-                filter=filter_)
+                ext_filter=filter_)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export PNG Image"), filter=filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export PNG Image"), ext_filter=filter_)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -6914,9 +6877,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption="Save Gerber source file",
                 caption="Save Gerber source file",
                 directory=self.get_last_save_folder() + '/' + name,
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Gerber source file"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Gerber source file"), ext_filter=_filter)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -6955,9 +6918,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption="Save Script source file",
                 caption="Save Script source file",
                 directory=self.get_last_save_folder() + '/' + name,
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Script source file"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Script source file"), ext_filter=_filter)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -6996,9 +6959,10 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption="Save Document source file",
                 caption="Save Document source file",
                 directory=self.get_last_save_folder() + '/' + name,
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Document source file"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Document source file"),
+                                                               ext_filter=_filter)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -7037,9 +7001,10 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Save Excellon source file"),
                 caption=_("Save Excellon source file"),
                 directory=self.get_last_save_folder() + '/' + name,
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Excellon source file"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Save Excellon source file"), ext_filter=_filter)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -7078,9 +7043,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export Excellon"),
                 caption=_("Export Excellon"),
                 directory=self.get_last_save_folder() + '/' + name,
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Excellon"), filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Excellon"), ext_filter=_filter)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -7122,9 +7087,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export Gerber"),
                 caption=_("Export Gerber"),
                 directory=self.get_last_save_folder() + '/' + name,
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter_)
+                ext_filter=_filter_)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Gerber"), filter=_filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Gerber"), ext_filter=_filter_)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -7154,6 +7119,8 @@ class App(QtCore.QObject):
             self.inform.emit('[WARNING_NOTCL] %s' % _("No object selected."))
             self.inform.emit('[WARNING_NOTCL] %s' % _("No object selected."))
             msg = _("Please Select a Geometry object to export")
             msg = _("Please Select a Geometry object to export")
             msgbox = QtWidgets.QMessageBox()
             msgbox = QtWidgets.QMessageBox()
+            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
             msgbox.setInformativeText(msg)
             msgbox.setInformativeText(msg)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             msgbox.setDefaultButton(bt_ok)
             msgbox.setDefaultButton(bt_ok)
@@ -7164,6 +7131,8 @@ class App(QtCore.QObject):
         if not isinstance(obj, GeometryObject):
         if not isinstance(obj, GeometryObject):
             msg = '[ERROR_NOTCL] %s' % _("Only Geometry objects can be used.")
             msg = '[ERROR_NOTCL] %s' % _("Only Geometry objects can be used.")
             msgbox = QtWidgets.QMessageBox()
             msgbox = QtWidgets.QMessageBox()
+            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
             msgbox.setInformativeText(msg)
             msgbox.setInformativeText(msg)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             msgbox.setDefaultButton(bt_ok)
             msgbox.setDefaultButton(bt_ok)
@@ -7178,9 +7147,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export DXF"),
                 caption=_("Export DXF"),
                 directory=self.get_last_save_folder() + '/' + name,
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter_)
+                ext_filter=_filter_)
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export DXF"), filter=_filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export DXF"), ext_filter=_filter_)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -7630,10 +7599,10 @@ class App(QtCore.QObject):
                 caption=_("Save Project As ..."),
                 caption=_("Save Project As ..."),
                 directory='{l_save}/{proj}_{date}'.format(l_save=str(self.get_last_save_folder()), date=self.date,
                 directory='{l_save}/{proj}_{date}'.format(l_save=str(self.get_last_save_folder()), date=self.date,
                                                           proj=_("Project")),
                                                           proj=_("Project")),
-                filter=filter_
+                ext_filter=filter_
             )
             )
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Project As ..."), filter=filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Project As ..."), ext_filter=filter_)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -7685,10 +7654,10 @@ class App(QtCore.QObject):
                 directory='{l_save}/{obj_name}_{date}'.format(l_save=str(self.get_last_save_folder()),
                 directory='{l_save}/{obj_name}_{date}'.format(l_save=str(self.get_last_save_folder()),
                                                               obj_name=obj_name,
                                                               obj_name=obj_name,
                                                               date=self.date),
                                                               date=self.date),
-                filter=filter_
+                ext_filter=filter_
             )
             )
         except TypeError:
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Object as PDF ..."), filter=filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Save Object as PDF ..."), ext_filter=filter_)
 
 
         filename = str(filename)
         filename = str(filename)
 
 
@@ -9892,12 +9861,12 @@ class App(QtCore.QObject):
         """
         """
         Shows a message on the FlatCAM Shell
         Shows a message on the FlatCAM Shell
 
 
-        :param msg: Message to display.
-        :param show: Opens the shell.
-        :param error: Shows the message as an error.
-        :param warning: Shows the message as an warning.
-        :param success: Shows the message as an success.
-        :param selected: Indicate that something was selected on canvas
+        :param msg:         Message to display.
+        :param show:        Opens the shell.
+        :param error:       Shows the message as an error.
+        :param warning:     Shows the message as an warning.
+        :param success:     Shows the message as an success.
+        :param selected:    Indicate that something was selected on canvas
         :return: None
         :return: None
         """
         """
         if show:
         if show:

+ 1 - 1
Bookmark.py

@@ -292,7 +292,7 @@ class BookmarkManager(QtWidgets.QWidget):
                                                                 l_save=str(self.app.get_last_save_folder()),
                                                                 l_save=str(self.app.get_last_save_folder()),
                                                                 n=_("Bookmarks"),
                                                                 n=_("Bookmarks"),
                                                                 date=date),
                                                                 date=date),
-                                                           filter=filter__)
+                                                           ext_filter=filter__)
 
 
         filename = str(filename)
         filename = str(filename)
 
 

+ 51 - 0
CHANGELOG.md

@@ -7,12 +7,63 @@ CHANGELOG for FlatCAM beta
 
 
 =================================================
 =================================================
 
 
+30.05.2020
+
+- made confirmation messages for the values that are modified not to be printed in the Shell
+- Isolation Tool: working on the Rest machining: almost there, perhaps I will use multiprocessing
+- Isolation Tool: removed the tools that have empty geometry in case of rest machining
+- Isolation Tool: solved some naming issues
+- Isolation Tool: updated the tools dict with the common parameters value on isolating
+- Fixed a recent change that made the edited Geometry objects in the Geometry Editor not to be plotted after saving changes
+- modified the Tool Database such that when a tool shape is selected as 'V' any change in the Vdia or Vangle or CutZ parameters will update the tool diameter value
+- In Tool Isolation made sure that the use of ESC key while some processes are active will disconnect the mouse events that may be connected, correctly
+- optimized the Gerber UI
+- added a Multi-color checkbox for the Geometry UI (will color differently tool geometry when the geometry is multitool)
+- added a Multi-color checkbox for the Excellon UI (this way colors for each tool are easier to differentiate especially when the diameter is close)
+- made the Shell Dock always show docked
+- fixed NCC Tool behavior when selecting tools for Isolation operation
+
+29.05.2020
+
+- fixed the Tool Isolation when using the 'follow' parameter
+- in Isolation Tool when the Rest machining is checked the combine parameter is set True automatically because the rest machining concept make sense only when all tools are used together
+- some changes in the UI; added in the status bar an icon to control the Shell Dock
+- clicking on the activity icon will replot all objects
+- optimized UI in Tool Isolation
+- overloaded the App inform signal to allow not printing to shell if a second bool parameter is given; modified some GUI messages to use this feature
+- fixed the shell status label status on shell dock close from close button
+- refactored some methods from App class and moved them to plotcanvas (plotcanvaslegacy) class
+- added an label with icon in the status bar, clicking it will toggle (show status) of the X-Y axis on cavnas
+- optimized the UI, added to status bar an icon to toggle the axis 
+- updated the Etch Compensation Tool by adding a new possibility to compensate the lateral etch (manual value)
+- updated the Etch Compensation Tool such that the resulting Gerber object will have the apertures attributes ('size', 'width', 'height') updated to the changes
+
+28.05.2020
+
+- made the visibility change (when using the Spacebar key in Project Tab) to be not threaded and to use the enabled property of the ShapesCollection which should be faster
+- updated the Tool Database class to have the Isolation Tool data
+- Isolation Tool - made to work the adding of tools from database
+- fixed some issues related to using the new Numerical... GUI elements
+- fixed issues in the Tool Subtract
+- remade Tool Subtract to use multiprocessing when processing geometry
+- the resulting Gerber file from Tool Subtract has now the attribute source_file populated
+
+27.05.2020
+
+- working on Isolation Tool: made to work the Isolation with multiple tools without rest machining
+
 26.05.2020
 26.05.2020
 
 
 - working on Isolation Tool: made to work the tool parameters data to GUI and GUI to data
 - working on Isolation Tool: made to work the tool parameters data to GUI and GUI to data
 - Isolation Tool: reworked the GUI
 - Isolation Tool: reworked the GUI
 - if there is a Gerber object selected then in Isolation Tool the Gerber object combobox will show that object name as current
 - if there is a Gerber object selected then in Isolation Tool the Gerber object combobox will show that object name as current
 - made the Project Tree items not editable by clicking on selected Tree items (the object rename can still be done in the Selected tab)
 - made the Project Tree items not editable by clicking on selected Tree items (the object rename can still be done in the Selected tab)
+- working on Isolation Tool: added a Preferences section in Edit -> Preferences and updated their usage within the Isolation tool
+- fixed milling drills not plotting the resulting Geometry object
+- all tuple entries in the Preferences UI are now protected against letter entry
+- all entries in the Preferences UI that have numerical entry are protected now against letters
+- cleaned the Preferences UI in the Gerber area
+- minor UI changes
 
 
 25.05.2020
 25.05.2020
 
 

+ 2 - 2
Common.py

@@ -572,7 +572,7 @@ class ExclusionAreas(QtCore.QObject):
         AppTool.delete_moving_selection_shape(self)
         AppTool.delete_moving_selection_shape(self)
         self.app.delete_selection_shape()
         self.app.delete_selection_shape()
         AppTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
         AppTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
-        self.app.inform.emit('[success] %s' % _("All exclusion zones deleted."))
+        self.app.inform.emit('%s' % _("All exclusion zones deleted."))
 
 
     def delete_sel_shapes(self, idxs):
     def delete_sel_shapes(self, idxs):
         """
         """
@@ -621,7 +621,7 @@ class ExclusionAreas(QtCore.QObject):
                                             """)
                                             """)
             self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object."))
             self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object."))
 
 
-            self.app.inform.emit('[success] %s' % _("All exclusion zones deleted."))
+            self.app.inform.emit('%s' % _("All exclusion zones deleted."))
 
 
     def travel_coordinates(self, start_point, end_point, tooldia):
     def travel_coordinates(self, start_point, end_point, tooldia):
         """
         """

BIN
assets/resources/axis16.png


BIN
assets/resources/grid32.png


BIN
assets/resources/grid_lines32.png


BIN
assets/resources/shell20.png


+ 17 - 6
camlib.py

@@ -4143,7 +4143,8 @@ class CNCjob(Geometry):
         self.dwell = dwell
         self.dwell = dwell
         self.dwelltime = float(dwelltime) if dwelltime is not None else self.app.defaults["geometry_dwelltime"]
         self.dwelltime = float(dwelltime) if dwelltime is not None else self.app.defaults["geometry_dwelltime"]
 
 
-        self.startz = float(startz) if startz is not None else self.app.defaults["geometry_startz"]
+        self.startz = float(startz) if startz is not None and startz != '' else self.app.defaults["geometry_startz"]
+
         self.z_end = float(endz) if endz is not None else self.app.defaults["geometry_endz"]
         self.z_end = float(endz) if endz is not None else self.app.defaults["geometry_endz"]
 
 
         self.xy_end = endxy if endxy != '' and endxy else self.app.defaults["geometry_endxy"]
         self.xy_end = endxy if endxy != '' and endxy else self.app.defaults["geometry_endxy"]
@@ -4374,7 +4375,7 @@ class CNCjob(Geometry):
 
 
                     self.gcode += self.create_gcode_multi_pass(geo, current_tooldia, extracut, self.extracut_length,
                     self.gcode += self.create_gcode_multi_pass(geo, current_tooldia, extracut, self.extracut_length,
                                                                tolerance,
                                                                tolerance,
-                                                               z_move=z_move,_postproc=p,
+                                                               z_move=z_move, postproc=p,
                                                                old_point=current_pt)
                                                                old_point=current_pt)
 
 
                 # calculate the travel distance
                 # calculate the travel distance
@@ -4831,15 +4832,25 @@ class CNCjob(Geometry):
         # Current path: temporary storage until tool is
         # Current path: temporary storage until tool is
         # lifted or lowered.
         # lifted or lowered.
         if self.toolchange_xy_type == "excellon":
         if self.toolchange_xy_type == "excellon":
-            if self.app.defaults["excellon_toolchangexy"] == '':
+            if self.app.defaults["excellon_toolchangexy"] == '' or self.app.defaults["excellon_toolchangexy"] is None:
                 pos_xy = (0, 0)
                 pos_xy = (0, 0)
             else:
             else:
-                pos_xy = [float(eval(a)) for a in self.app.defaults["excellon_toolchangexy"].split(",")]
+                pos_xy = self.app.defaults["excellon_toolchangexy"]
+                try:
+                    pos_xy = [float(eval(a)) for a in pos_xy.split(",")]
+                except Exception:
+                    if len(pos_xy) != 2:
+                        pos_xy = (0, 0)
         else:
         else:
-            if self.app.defaults["geometry_toolchangexy"] == '':
+            if self.app.defaults["geometry_toolchangexy"] == '' or self.app.defaults["geometry_toolchangexy"] is None:
                 pos_xy = (0, 0)
                 pos_xy = (0, 0)
             else:
             else:
-                pos_xy = [float(eval(a)) for a in self.app.defaults["geometry_toolchangexy"].split(",")]
+                pos_xy = self.app.defaults["geometry_toolchangexy"]
+                try:
+                    pos_xy = [float(eval(a)) for a in pos_xy.split(",")]
+                except Exception:
+                    if len(pos_xy) != 2:
+                        pos_xy = (0, 0)
 
 
         path = [pos_xy]
         path = [pos_xy]
         # path = [(0, 0)]
         # path = [(0, 0)]

+ 26 - 13
defaults.py

@@ -172,12 +172,6 @@ class FlatCAMDefaults:
                                "All Files (*.*)",
                                "All Files (*.*)",
 
 
         # Gerber Options
         # Gerber Options
-        "gerber_isotooldia": 0.1,
-        "gerber_isopasses": 1,
-        "gerber_isooverlap": 10,
-        "gerber_milling_type": "cl",
-        "gerber_combine_passes": False,
-        "gerber_iso_scope": _("All"),
         "gerber_noncoppermargin": 0.1,
         "gerber_noncoppermargin": 0.1,
         "gerber_noncopperrounded": False,
         "gerber_noncopperrounded": False,
         "gerber_bboxmargin": 0.1,
         "gerber_bboxmargin": 0.1,
@@ -188,11 +182,6 @@ class FlatCAMDefaults:
         "gerber_aperture_scale_factor": 1.0,
         "gerber_aperture_scale_factor": 1.0,
         "gerber_aperture_buffer_factor": 0.0,
         "gerber_aperture_buffer_factor": 0.0,
         "gerber_follow": False,
         "gerber_follow": False,
-        "gerber_tool_type": 'C1',
-        "gerber_vtipdia": 0.1,
-        "gerber_vtipangle": 30,
-        "gerber_vcutz": -0.05,
-        "gerber_iso_type": "full",
         "gerber_buffering": "full",
         "gerber_buffering": "full",
         "gerber_simplification": False,
         "gerber_simplification": False,
         "gerber_simp_tolerance": 0.0005,
         "gerber_simp_tolerance": 0.0005,
@@ -223,6 +212,7 @@ class FlatCAMDefaults:
         # Excellon General
         # Excellon General
         "excellon_plot": True,
         "excellon_plot": True,
         "excellon_solid": True,
         "excellon_solid": True,
+        "excellon_multicolored": False,
         "excellon_format_upper_in": 2,
         "excellon_format_upper_in": 2,
         "excellon_format_lower_in": 4,
         "excellon_format_lower_in": 4,
         "excellon_format_upper_mm": 3,
         "excellon_format_upper_mm": 3,
@@ -311,6 +301,7 @@ class FlatCAMDefaults:
 
 
         # Geometry General
         # Geometry General
         "geometry_plot": True,
         "geometry_plot": True,
+        "geometry_multicolored": False,
         "geometry_circle_steps": 64,
         "geometry_circle_steps": 64,
         "geometry_cnctooldia": "2.4",
         "geometry_cnctooldia": "2.4",
         "geometry_plot_line": "#FF0000",
         "geometry_plot_line": "#FF0000",
@@ -391,6 +382,28 @@ class FlatCAMDefaults:
         "cncjob_annotation_fontsize": 9,
         "cncjob_annotation_fontsize": 9,
         "cncjob_annotation_fontcolor": '#990000',
         "cncjob_annotation_fontcolor": '#990000',
 
 
+        # Isolation Routing Tool
+        "tools_iso_tooldia": "0.1",
+        "tools_iso_order": 'rev',
+        "tools_iso_tool_type": 'C1',
+        "tools_iso_tool_vtipdia": 0.1,
+        "tools_iso_tool_vtipangle": 30,
+        "tools_iso_tool_cutz": -0.05,
+        "tools_iso_newdia": 0.1,
+
+        "tools_iso_passes": 1,
+        "tools_iso_overlap": 10,
+        "tools_iso_milling_type": "cl",
+        "tools_iso_follow": False,
+        "tools_iso_isotype": "full",
+
+        "tools_iso_rest":           False,
+        "tools_iso_combine_passes": False,
+        "tools_iso_isoexcept":      False,
+        "tools_iso_selection":      _("All"),
+        "tools_iso_area_shape":     "square",
+        "tools_iso_plotting":       'normal',
+
         # NCC Tool
         # NCC Tool
         "tools_ncctools": "1.0, 0.5",
         "tools_ncctools": "1.0, 0.5",
         "tools_nccorder": 'rev',
         "tools_nccorder": 'rev',
@@ -405,13 +418,13 @@ class FlatCAMDefaults:
         "tools_ncc_offset_value": 0.0000,
         "tools_ncc_offset_value": 0.0000,
         "tools_nccref": _('Itself'),
         "tools_nccref": _('Itself'),
         "tools_ncc_area_shape": "square",
         "tools_ncc_area_shape": "square",
-        "tools_ncc_plotting": 'normal',
         "tools_nccmilling_type": 'cl',
         "tools_nccmilling_type": 'cl',
         "tools_ncctool_type": 'C1',
         "tools_ncctool_type": 'C1',
         "tools_ncccutz": -0.05,
         "tools_ncccutz": -0.05,
         "tools_ncctipdia": 0.1,
         "tools_ncctipdia": 0.1,
         "tools_ncctipangle": 30,
         "tools_ncctipangle": 30,
         "tools_nccnewdia": 0.1,
         "tools_nccnewdia": 0.1,
+        "tools_ncc_plotting": 'normal',
 
 
         # Cutout Tool
         # Cutout Tool
         "tools_cutouttooldia": 2.4,
         "tools_cutouttooldia": 2.4,
@@ -430,7 +443,7 @@ class FlatCAMDefaults:
         "tools_paintoverlap": 20,
         "tools_paintoverlap": 20,
         "tools_paintmargin": 0.0,
         "tools_paintmargin": 0.0,
         "tools_paintmethod": _("Seed"),
         "tools_paintmethod": _("Seed"),
-        "tools_selectmethod": _("All Polygons"),
+        "tools_selectmethod": _("All"),
         "tools_paint_area_shape": "square",
         "tools_paint_area_shape": "square",
         "tools_pathconnect": True,
         "tools_pathconnect": True,
         "tools_paintcontour": True,
         "tools_paintcontour": True,

+ 1 - 1
tclCommands/TclCommandIsolate.py

@@ -89,7 +89,7 @@ class TclCommandIsolate(TclCommandSignaled):
                 par = args['combine']
                 par = args['combine']
             args['combine'] = bool(eval(par))
             args['combine'] = bool(eval(par))
         else:
         else:
-            args['combine'] = bool(eval(self.app.defaults["gerber_combine_passes"]))
+            args['combine'] = bool(eval(self.app.defaults["tools_iso_combine_passes"]))
 
 
         obj = self.app.collection.get_by_name(name)
         obj = self.app.collection.get_by_name(name)
         if obj is None:
         if obj is None:

+ 1 - 1
tclCommands/TclCommandPaint.py

@@ -159,7 +159,7 @@ class TclCommandPaint(TclCommand):
 
 
         # used only to have correct information's in the obj.tools[tool]['data'] dict
         # used only to have correct information's in the obj.tools[tool]['data'] dict
         if "all" in args:
         if "all" in args:
-            select = _("All Polygons")
+            select = _("All")
         elif "single" in args:
         elif "single" in args:
             select = _("Polygon Selection")
             select = _("Polygon Selection")
         else:
         else:

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