Przeglądaj źródła

Merged Beta into Beta_8.993

Marius Stanciu 5 lat temu
rodzic
commit
47dd8b0dfe
62 zmienionych plików z 2143 dodań i 2304 usunięć
  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 datetime import datetime
+import math
+
 import gettext
 import AppTranslation as fcTranslate
 import builtins
@@ -655,7 +657,7 @@ class ToolsDB(QtWidgets.QWidget):
                                                                l_save=str(self.app.get_last_save_folder()),
                                                                n=_("Tools_Database"),
                                                                date=date),
-                                                           filter=filter__)
+                                                           ext_filter=filter__)
 
         filename = str(filename)
 
@@ -1030,6 +1032,7 @@ class ToolsDB2(QtWidgets.QWidget):
         self.advanced_box.setTitle(_("Advanced Geo Parameters"))
         self.advanced_box.setFixedWidth(250)
 
+        # NCC TOOL BOX
         self.ncc_box = QtWidgets.QGroupBox()
         self.ncc_box.setStyleSheet("""
                         QGroupBox
@@ -1042,6 +1045,7 @@ class ToolsDB2(QtWidgets.QWidget):
         self.ncc_box.setTitle(_("NCC Parameters"))
         self.ncc_box.setFixedWidth(250)
 
+        # PAINT TOOL BOX
         self.paint_box = QtWidgets.QGroupBox()
         self.paint_box.setStyleSheet("""
                         QGroupBox
@@ -1054,10 +1058,24 @@ class ToolsDB2(QtWidgets.QWidget):
         self.paint_box.setTitle(_("Paint Parameters"))
         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.advanced_box.setLayout(self.advanced_vlay)
         self.ncc_box.setLayout(self.ncc_vlay)
         self.paint_box.setLayout(self.paint_vlay)
+        self.iso_box.setLayout(self.iso_vlay)
 
         geo_vlay = QtWidgets.QVBoxLayout()
         geo_vlay.addWidget(self.basic_box)
@@ -1067,6 +1085,7 @@ class ToolsDB2(QtWidgets.QWidget):
         tools_vlay = QtWidgets.QVBoxLayout()
         tools_vlay.addWidget(self.ncc_box)
         tools_vlay.addWidget(self.paint_box)
+        tools_vlay.addWidget(self.iso_box)
         tools_vlay.addStretch()
 
         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.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
@@ -1743,6 +1857,13 @@ class ToolsDB2(QtWidgets.QWidget):
             "tools_paintmethod":        self.paintmethod_combo,
             "tools_pathconnect":        self.pathconnect_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 = {
@@ -1787,6 +1908,13 @@ class ToolsDB2(QtWidgets.QWidget):
             'gdb_p_method':         "tools_paintmethod",
             'gdb_p_connect':        "tools_pathconnect",
             '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
@@ -1944,6 +2072,7 @@ class ToolsDB2(QtWidgets.QWidget):
                 self.advanced_box.setEnabled(True)
                 self.ncc_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.setFocus()
@@ -1954,6 +2083,7 @@ class ToolsDB2(QtWidgets.QWidget):
                 self.advanced_box.setEnabled(False)
                 self.ncc_box.setEnabled(False)
                 self.paint_box.setEnabled(False)
+                self.iso_box.setEnabled(False)
         else:
             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_pathconnect":        self.app.defaults["tools_pathconnect"],
             "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 = {}
@@ -2117,7 +2254,7 @@ class ToolsDB2(QtWidgets.QWidget):
                                                                 l_save=str(self.app.get_last_save_folder()),
                                                                 n=_("Tools_Database"),
                                                                 date=date),
-                                                           filter=filter__)
+                                                           ext_filter=filter__)
 
         filename = str(filename)
 
@@ -2218,6 +2355,18 @@ class ToolsDB2(QtWidgets.QWidget):
         self.app.tools_db_changed_flag = False
         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):
         # make sure that we don't make multiple connections to the widgets
         self.ui_disconnect()
@@ -2247,12 +2396,40 @@ class ToolsDB2(QtWidgets.QWidget):
             if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
                 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):
         try:
             self.name_entry.editingFinished.disconnect(self.update_tree_name)
         except (TypeError, AttributeError):
             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:
             wdg = self.form_fields[key]
 
@@ -2398,6 +2575,18 @@ class ToolsDB2(QtWidgets.QWidget):
             elif wdg_name == "gdb_p_contour":
                 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()
 
     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.setVisible(True)
-        # self.app.ui.snap_toolbar.setDisabled(False)
+        # self.app.ui.status_toolbar.setDisabled(False)
 
         # start with GRID toolbar activated
         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.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.cmenu_newmenu.menuAction().setVisible(False)
@@ -4135,11 +4135,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # make sure that the cursor shape is enabled/disabled, too
         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
         else:
             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):
         """

+ 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.setVisible(True)
-        # self.app.ui.snap_toolbar.setDisabled(False)
+        # self.app.ui.status_toolbar.setDisabled(False)
 
         # start with GRID toolbar activated
         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(
                 caption=_("Export Code ..."),
                 directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
-                filter=_filter_
+                ext_filter=_filter_
             )[0])
         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 == "":
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))

+ 36 - 12
AppGUI/GUIElements.py

@@ -570,10 +570,13 @@ class FCEntry3(FCEntry):
 
 
 class EvalEntry(QtWidgets.QLineEdit):
-    def __init__(self, parent=None):
+    def __init__(self, border_color=None, parent=None):
         super(EvalEntry, self).__init__(parent)
         self.readyToEdit = True
 
+        if border_color:
+            self.setStyleSheet("QLineEdit {border: 1px solid %s;}" % border_color)
+
         self.editingFinished.connect(self.on_edit_finished)
 
     def on_edit_finished(self):
@@ -639,7 +642,7 @@ class EvalEntry2(QtWidgets.QLineEdit):
 
     def get_value(self):
         raw = str(self.text()).strip(' ')
-        evaled = 0.0
+
         try:
             evaled = eval(raw)
         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: /,*,+,-,%
     """
-    def __init__(self):
-        super().__init__()
+    def __init__(self, border_color=None):
+        super().__init__(border_color=border_color)
 
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s]*")
         validator = QtGui.QRegExpValidator(regex, self)
         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: /,*,+,-,%
     """
-    def __init__(self):
-        super().__init__()
+    def __init__(self, border_color=None):
+        super().__init__(border_color=border_color)
 
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s\,]*")
         validator = QtGui.QRegExpValidator(regex, self)
@@ -2551,7 +2554,7 @@ class DialogBoxRadio(QtWidgets.QDialog):
         :param title: string with the window title
         :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:
             self.location = str((0, 0))
         else:
@@ -2794,9 +2797,12 @@ class MyCompleter(QCompleter):
     insertText = QtCore.pyqtSignal(str)
 
     def __init__(self, parent=None):
-        QCompleter.__init__(self)
+        QCompleter.__init__(self, parent=parent)
         self.setCompletionMode(QCompleter.PopupCompletion)
         self.highlighted.connect(self.setHighlighted)
+
+        self.lastSelected = ''
+
         # self.popup().installEventFilter(self)
 
     # def eventFilter(self, obj, event):
@@ -2952,9 +2958,9 @@ class FCFileSaveDialog(QtWidgets.QFileDialog):
         super(FCFileSaveDialog, self).__init__(*args)
 
     @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,
-                                                                  directory=directory, filter=filter,
+                                                                  directory=directory, filter=ext_filter,
                                                                   initialFilter=initialFilter)
 
         filename = str(filename)
@@ -2970,6 +2976,22 @@ class FCFileSaveDialog(QtWidgets.QFileDialog):
             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):
     """
     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.icon_path = icon
 
-        self.icon = QtWidgets.QLabel(self)
+        self.icon = FCLabel(self)
         self.icon.setGeometry(0, 0, 16, 12)
         self.movie = QtGui.QMovie(self.movie_path)
 
@@ -3019,6 +3041,8 @@ class FlatCAMActivityView(QtWidgets.QWidget):
 
         layout.addWidget(self.text)
 
+        self.icon.clicked.connect(self.app.on_toolbar_replot)
+
     def set_idle(self):
         self.movie.stop()
         self.text.setText(_("Idle."))

+ 144 - 109
AppGUI/MainGUI.py

@@ -58,17 +58,6 @@ class MainGUI(QtWidgets.QMainWindow):
         # ############ 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 ##################################
         # #######################################################################
@@ -200,9 +189,9 @@ class MainGUI(QtWidgets.QMainWindow):
         self.menufilerunscript = QtWidgets.QAction(
             QtGui.QIcon(self.app.resource_location + '/script16.png'), '%s\tShift+S' % _('Run Script ...'), self)
         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.menufileopenscript)
@@ -265,9 +254,9 @@ class MainGUI(QtWidgets.QMainWindow):
         self.menufileexportexcellon = QtWidgets.QAction(
             QtGui.QIcon(self.app.resource_location + '/drill32.png'), _('Export &Excellon ...'), self)
         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)
 
@@ -344,16 +333,16 @@ class MainGUI(QtWidgets.QMainWindow):
         self.menuedit_convertjoin = self.menuedit_convert.addAction(
             QtGui.QIcon(self.app.resource_location + '/join16.png'), _('&Join Geo/Gerber/Exc -> Geo'))
         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(
             QtGui.QIcon(self.app.resource_location + '/join16.png'), _('Join Excellon(s) -> Excellon'))
         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(
             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(
             QtGui.QIcon(self.app.resource_location + '/convert24.png'), _('Convert Single to MultiGeo'))
         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(
             QtGui.QIcon(self.app.resource_location + '/convert24.png'), _('Convert Multi to SingleGeo'))
         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
         self.menuedit_convert.addSeparator()
@@ -488,7 +477,7 @@ class MainGUI(QtWidgets.QMainWindow):
         self.menuview_toggle_grid = self.menuview.addAction(
             QtGui.QIcon(self.app.resource_location + '/grid32.png'), _("&Toggle Grid Snap\tG"))
         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(
             QtGui.QIcon(self.app.resource_location + '/axis32.png'), _("&Toggle Axis\tShift+G"))
         self.menuview_toggle_workspace = self.menuview.addAction(
@@ -825,10 +814,10 @@ class MainGUI(QtWidgets.QMainWindow):
         self.grb_edit_toolbar.setObjectName('GrbEditor_TB')
         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 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
-        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'))
         self.grid_gap_x_entry = FCEntry2()
         self.grid_gap_x_entry.setMaximumWidth(70)
         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.setMaximumWidth(70)
         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.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'))
 
         self.snap_max_dist_entry = FCEntry()
         self.snap_max_dist_entry.setMaximumWidth(70)
         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.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 # ##################################
         # ########################################################################
@@ -1489,8 +1501,7 @@ class MainGUI(QtWidgets.QMainWindow):
             QtGui.QIcon(self.app.resource_location + '/resize16.png'), _("Resize Drill"))
 
         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(
             QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete"))
         self.popmenu_edit = self.popMenu.addAction(
@@ -1513,19 +1524,19 @@ class MainGUI(QtWidgets.QMainWindow):
         self.infobar.addWidget(self.fcinfo, stretch=1)
 
         # 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.setToolTip(_("Relative measurement.\nReference is last click position"))
         # 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.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.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.setToolTip(_("HUD (Heads up display)"))
@@ -1533,10 +1544,14 @@ class MainGUI(QtWidgets.QMainWindow):
         self.infobar.addWidget(self.hud_label)
 
         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.infobar.addWidget(self.wplace_label)
 
         self.units_label = QtWidgets.QLabel("[mm]")
+        self.units_label.setToolTip(_("Application units"))
         self.units_label.setMargin(2)
         self.infobar.addWidget(self.units_label)
 
@@ -1655,7 +1670,7 @@ class MainGUI(QtWidgets.QMainWindow):
         self.clear_btn.clicked.connect(self.on_gui_clear)
 
         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
         # 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)
 
         # if tb & 128:
-        #     self.ui.snap_toolbar.setVisible(True)
+        #     self.ui.status_toolbar.setVisible(True)
         # else:
-        #     self.ui.snap_toolbar.setVisible(False)
+        #     self.ui.status_toolbar.setVisible(False)
 
         # Grid Toolbar is always active now
-        self.snap_toolbar.setVisible(True)
+        self.status_toolbar.setVisible(True)
 
         if tb & 256:
             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.setWindowTitle(_("Clear GUI Settings"))
         msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/trash32.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
         bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
 
@@ -2235,11 +2252,11 @@ class MainGUI(QtWidgets.QMainWindow):
 
                 # Toggle axis
                 if key == QtCore.Qt.Key_G:
-                    self.app.on_toggle_axis()
+                    self.app.plotcanvas.on_toggle_axis()
 
                 # Toggle HUD (Heads-Up Display)
                 if key == QtCore.Qt.Key_H:
-                    self.app.on_toggle_hud()
+                    self.app.plotcanvas.on_toggle_hud()
                 # Locate in Object
                 if key == QtCore.Qt.Key_J:
                     self.app.on_locate(obj=self.app.collection.get_active())
@@ -2318,7 +2335,7 @@ class MainGUI(QtWidgets.QMainWindow):
 
                 # Toggle Grid lines
                 if key == QtCore.Qt.Key_G:
-                    self.app.on_toggle_grid_lines()
+                    self.app.plotcanvas.on_toggle_grid_lines()
                     return
 
                 # Punch Gerber Tool
@@ -2488,7 +2505,7 @@ class MainGUI(QtWidgets.QMainWindow):
                         if active_index == 0:
                             self.app.collection.set_active(names_list[-1])
                         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
                 if key == QtCore.Qt.Key_Down:
@@ -2503,7 +2520,7 @@ class MainGUI(QtWidgets.QMainWindow):
                         if active_index == len(names_list) - 1:
                             self.app.collection.set_active(names_list[0])
                         else:
-                            self.app.collection.set_active(names_list[active_index+1])
+                            self.app.collection.set_active(names_list[active_index + 1])
 
                 # New Geometry
                 if key == QtCore.Qt.Key_B:
@@ -2627,6 +2644,8 @@ class MainGUI(QtWidgets.QMainWindow):
                         messagebox.setText(msg)
                         messagebox.setWindowTitle(_("Warning"))
                         messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
+                        messagebox.setIcon(QtWidgets.QMessageBox.Question)
+
                         messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                         messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                         messagebox.exec_()
@@ -2789,6 +2808,8 @@ class MainGUI(QtWidgets.QMainWindow):
                             messagebox.setText(msg)
                             messagebox.setWindowTitle(_("Warning"))
                             messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
+                            messagebox.setIcon(QtWidgets.QMessageBox.Warning)
+
                             messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                             messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                             messagebox.exec_()
@@ -2834,6 +2855,8 @@ class MainGUI(QtWidgets.QMainWindow):
                             messagebox.setText(msg)
                             messagebox.setWindowTitle(_("Warning"))
                             messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
+                            messagebox.setIcon(QtWidgets.QMessageBox.Warning)
+
                             messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                             messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                             messagebox.exec_()
@@ -2854,6 +2877,8 @@ class MainGUI(QtWidgets.QMainWindow):
                             messagebox.setText(msg)
                             messagebox.setWindowTitle(_("Warning"))
                             messagebox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/warning.png'))
+                            messagebox.setIcon(QtWidgets.QMessageBox.Warning)
+
                             messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                             messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                             messagebox.exec_()
@@ -3569,7 +3594,7 @@ class MainGUI(QtWidgets.QMainWindow):
             # hide all Toolbars
             for tb in self.findChildren(QtWidgets.QToolBar):
                 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.toggle_fscreen = True
@@ -3629,8 +3654,18 @@ class MainGUI(QtWidgets.QMainWindow):
         if self.shell_dock.isVisible():
             self.shell_dock.hide()
             self.app.plotcanvas.native.setFocus()
+            self.shell_status_label.setStyleSheet("")
+            self.app.inform[str, bool].emit(_("Shell disabled."), False)
         else:
             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
             # self.shell._edit.setFocus()
@@ -3671,7 +3706,7 @@ class ShortcutsTab(QtWidgets.QWidget):
         self.sh_tab_layout.addLayout(self.sh_hlay)
 
         self.app_sh_msg = (
-            '''<b>%s</b><br>
+                '''<b>%s</b><br>
             <table border="0" cellpadding="0" cellspacing="0" style="width:283px">
                 <tbody>
                     <tr height="20">
@@ -4040,53 +4075,53 @@ class ShortcutsTab(QtWidgets.QWidget):
                 </tbody>
             </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()

+ 89 - 316
AppGUI/ObjectUI.py

@@ -114,7 +114,7 @@ class ObjectUI(QtWidgets.QWidget):
             self.common_grid.addWidget(self.transform_label, 2, 0, 1, 2)
 
             # ### Scale ####
-            self.scale_entry = NumericalEvalEntry()
+            self.scale_entry = NumericalEvalEntry(border_color='#0069A9')
             self.scale_entry.set_value(1.0)
             self.scale_entry.setToolTip(
                 _("Factor by which to multiply\n"
@@ -132,7 +132,7 @@ class ObjectUI(QtWidgets.QWidget):
             self.common_grid.addWidget(self.scale_button, 3, 1)
 
             # ### Offset ####
-            self.offsetvector_entry = NumericalEvalTupleEntry()
+            self.offsetvector_entry = NumericalEvalTupleEntry(border_color='#0069A9')
             self.offsetvector_entry.setText("(0.0, 0.0)")
             self.offsetvector_entry.setToolTip(
                 _("Amount by which to move the object\n"
@@ -153,17 +153,20 @@ class ObjectUI(QtWidgets.QWidget):
     
     def confirmation_message(self, accepted, minval, maxval):
         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:
-            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):
         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:
-            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):
@@ -205,27 +208,37 @@ class GerberObjectUI(ObjectUI):
         self.multicolored_cb.setMinimumWidth(55)
         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
         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"))
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_hlay.addWidget(name_label)
         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()
         self.custom_box.addLayout(hlay_plot)
 
         # ### 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(
             _("Apertures Table for the Gerber Object.")
         )
@@ -282,253 +295,7 @@ class GerberObjectUI(ObjectUI):
         # start with apertures table hidden
         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.setToolTip(
             _("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"
               "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 #######################
@@ -562,14 +322,7 @@ class GerberObjectUI(ObjectUI):
         self.tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("TOOLS"))
         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.setToolTip(
             _("Create a Geometry object with\n"
@@ -581,17 +334,9 @@ class GerberObjectUI(ObjectUI):
                                           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
-        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.setToolTip(
             _("Create the Geometry Object\n"
@@ -603,17 +348,9 @@ class GerberObjectUI(ObjectUI):
                             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
-        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.setToolTip(
             _("Generate the geometry for\n"
@@ -625,8 +362,7 @@ class GerberObjectUI(ObjectUI):
                             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.setFrameShape(QtWidgets.QFrame.HLine)
@@ -744,22 +480,38 @@ class ExcellonObjectUI(ObjectUI):
                           parent=parent,
                           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.setMinimumWidth(90)
+
+        grid_h.addWidget(self.plot_options_label, 0, 0)
+
+        # Solid CB
         self.solid_cb = FCCheckBox(label=_('Solid'))
         self.solid_cb.setToolTip(
             _("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
         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"))
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
@@ -1546,12 +1298,28 @@ class GeometryObjectUI(ObjectUI):
         )
 
         # 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.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
         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"))
         self.name_entry = FCEntry()
@@ -1715,19 +1483,24 @@ class GeometryObjectUI(ObjectUI):
         grid1.addWidget(self.addtool_entry_lbl, 3, 0)
         grid1.addWidget(self.addtool_entry, 3, 1)
 
+        bhlay = QtWidgets.QHBoxLayout()
+
         self.addtool_btn = QtWidgets.QPushButton(_('Add'))
         self.addtool_btn.setToolTip(
             _("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.setToolTip(
             _("Add a new tool to the Tool Table\n"
               "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.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 vispy.scene.visuals import InfiniteLine, Line, Rectangle, Text
 
+import gettext
+import AppTranslation as fcTranslate
+import builtins
+
 import numpy as np
 from vispy.geometry import Rect
 
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
 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,
                                    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
         if self.fcapp.defaults["global_cursor_color_enabled"]:
             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,
                                           parent=self.line_parent)
 
-        # HUD Display
-        self.hud_enabled = False
-
         # font size
         qsettings = QtCore.QSettings("Open Source", "FlatCAM")
         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)
         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
         if self.fcapp.defaults['global_hud'] is 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_collection = self.new_shape_collection()
@@ -201,27 +218,75 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
 
         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:
             self.hud_enabled = True
             self.rect_hud.parent = self.view
             self.text_hud.parent = self.view
-
             self.fcapp.defaults['global_hud'] = True
             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:
             self.hud_enabled = False
             self.rect_hud.parent = None
             self.text_hud.parent = None
-
             self.fcapp.defaults['global_hud'] = False
             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):
         """

+ 71 - 12
AppGUI/PlotCanvasLegacy.py

@@ -38,7 +38,6 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
-
 log = logging.getLogger('base')
 
 
@@ -310,6 +309,9 @@ class PlotCanvasLegacy(QtCore.QObject):
         self.hud_enabled = False
         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
         # all CNC have a limited workspace
         if self.app.defaults['global_workspace'] is True:
@@ -318,25 +320,62 @@ class PlotCanvasLegacy(QtCore.QObject):
         if self.app.defaults['global_hud'] is 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:
             self.hud_enabled = True
             self.text_hud.add_artist()
-
             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:
             self.hud_enabled = False
             self.text_hud.remove_artist()
-
             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()
 
@@ -399,6 +438,26 @@ class PlotCanvasLegacy(QtCore.QObject):
             if self.hud_holder in self.p.axes.artists:
                 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):
         """
         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 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_noncopperrounded": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_rounded_cb,
             "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_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_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_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,
@@ -180,6 +168,7 @@ class PreferencesUIManager:
             # Excellon General
             "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_multicolored": self.ui.excellon_defaults_form.excellon_gen_group.multicolored_cb,
             "excellon_format_upper_in":
                 self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry,
             "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 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_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_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_angle":
                 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,
 
             # 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_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_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_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_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_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
-            "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
-            "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,
 
+            # 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
-            "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
             "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.setWindowTitle(_("Application restart"))
             msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/warning.png'))
+            msgbox.setIcon(QtWidgets.QMessageBox.Question)
 
             bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
             msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.NoRole)
@@ -1111,7 +1124,7 @@ class PreferencesUIManager:
         if self.ui.grb_edit_toolbar.isVisible():
             tb_status += 64
 
-        if self.ui.snap_toolbar.isVisible():
+        if self.ui.status_toolbar.isVisible():
             tb_status += 128
 
         if self.ui.toolbarshell.isVisible():
@@ -1175,6 +1188,7 @@ class PreferencesUIManager:
                              "Do you want to save the Preferences?"))
             msgbox.setWindowTitle(_("Save Preferences"))
             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)
             msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)

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

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 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
 import gettext
 import AppTranslation as fcTranslate
@@ -60,7 +60,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
         toolchange_xy_label.setToolTip(
             _("Toolchange X,Y position.")
         )
-        self.toolchangexy_entry = FCEntry()
+        self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
 
         grid1.addWidget(toolchange_xy_label, 1, 0)
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
@@ -71,7 +71,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
             _("Height of the tool just after start.\n"
               "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(self.estartz_entry, 2, 1)

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

@@ -36,22 +36,31 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
         grid1 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid1)
 
+        # Plot CB
         self.plot_cb = FCCheckBox(label=_('Plot'))
         self.plot_cb.setToolTip(
             "Plot (show) this object."
         )
         grid1.addWidget(self.plot_cb, 0, 0)
 
+        # Solid CB
         self.solid_cb = FCCheckBox(label=_('Solid'))
         self.solid_cb.setToolTip(
             "Plot as solid circles."
         )
         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.setFrameShape(QtWidgets.QFrame.HLine)
         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()
         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 AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
-    FCComboBox
+    FCComboBox, NumericalEvalTupleEntry
 from AppGUI.preferences import machinist_setting
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
@@ -198,7 +198,7 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
               "If no value is entered then there is no move\n"
               "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(self.endxy_entry, 9, 1)

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

@@ -31,7 +31,7 @@ class ExcellonPreferencesUI(QtWidgets.QWidget):
         self.decimals = 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.setMinimumWidth(290)
         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'},
                                               ], 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_radio, 8, 1)
 

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

@@ -1,7 +1,8 @@
 from PyQt5 import QtWidgets
 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
 
 import gettext
@@ -46,8 +47,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         toolchange_xy_label.setToolTip(
             _("Toolchange X,Y position.")
         )
+        self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
+
         grid1.addWidget(toolchange_xy_label, 1, 0)
-        self.toolchangexy_entry = FCEntry()
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
 
         # Start move Z
@@ -56,8 +58,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
             _("Height of the tool just after starting the work.\n"
               "Delete the value if you don't need this feature.")
         )
+        self.gstartz_entry = NumericalEvalEntry(border_color='#0069A9')
+
         grid1.addWidget(startzlabel, 2, 0)
-        self.gstartz_entry = FloatEntry()
         grid1.addWidget(self.gstartz_entry, 2, 1)
 
         # Feedrate rapids
@@ -186,6 +189,11 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(segy_label, 11, 0)
         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 ----------
         # -----------------------------
@@ -195,10 +203,10 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
               "Those parameters are available only for\n"
               "Advanced App. Level.")
         )
-        grid1.addWidget(self.adv_label, 12, 0, 1, 2)
+        grid1.addWidget(self.adv_label, 13, 0, 1, 2)
 
         # Exclusion Area CB
-        self.exclusion_cb = FCCheckBox('%s:' % _("Exclusion areas"))
+        self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas"))
         self.exclusion_cb.setToolTip(
             _(
                 "Include exclusion areas.\n"
@@ -206,7 +214,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
                 "is forbidden."
             )
         )
-        grid1.addWidget(self.exclusion_cb, 13, 0, 1, 2)
+        grid1.addWidget(self.exclusion_cb, 14, 0, 1, 2)
 
         # Area Selection 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'},
                                           {'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
         self.strategy_label = FCLabel('%s:' % _("Strategy"))
@@ -229,8 +237,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
                                         {'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
         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.layout.addWidget(self.plot_options_label)
 
+        plot_hlay = QtWidgets.QHBoxLayout()
+        self.layout.addLayout((plot_hlay))
+
         # Plot CB
         self.plot_cb = FCCheckBox(label=_('Plot'))
         self.plot_cb.setToolTip(
             _("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()
         self.layout.addLayout(grid0)

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

@@ -1,7 +1,8 @@
 from PyQt5 import QtWidgets
 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.OptionsGroupUI import OptionsGroupUI
 
@@ -176,7 +177,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
               "If no value is entered then there is no move\n"
               "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(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)
         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_label = QtWidgets.QLabel('%s:' % _('Buffering'))
         buffering_label.setToolTip(

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

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 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
 
 import gettext
@@ -109,8 +109,9 @@ class GerberEditorPrefGroupUI(OptionsGroupUI):
               "The value of the diameter has to use the dot decimals separator.\n"
               "Valid values: 0.3, 1.0")
         )
+        self.adddim_entry = NumericalEvalTupleEntry(border_color='#0069A9')
+
         grid0.addWidget(self.adddim_label, 5, 0)
-        self.adddim_entry = FCEntry()
         grid0.addWidget(self.adddim_entry, 5, 1)
 
         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()
         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
         self.solid_cb = FCCheckBox(label='%s' % _('Solid'))
         self.solid_cb.setToolTip(
             _("Solid color polygons.")
         )
-        grid0.addWidget(self.solid_cb, 0, 0)
+        grid0.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.")
         )
-        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
         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")))
 
-        # ## 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
         self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
         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.addWidget(self.gerber_opt_group)
         self.vlay.addWidget(self.gerber_exp_group)
+        self.vlay.addStretch()
 
         self.layout.addWidget(self.gerber_gen_group)
         self.layout.addLayout(self.vlay)

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

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 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
 
 import gettext
@@ -116,7 +116,7 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
               "(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(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.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
 
 import gettext
@@ -45,7 +45,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
               "Valid values: 0.3, 1.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"))
         grid0.addWidget(self.ncc_tool_dia_entry, 0, 1)
 
@@ -285,7 +285,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(separator_line, 16, 0, 1, 2)
 
         # Rest machining CheckBox
-        self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining"))
+        self.ncc_rest_cb = FCCheckBox('%s' % _("Rest"))
         self.ncc_rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\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)
 
         # ## 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(
-            _("- '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(self.ncc_plotting_radio, 21, 1)
+        grid0.addWidget(self.plotting_radio, 21, 1)
 
         self.layout.addStretch()

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

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 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
 
 import gettext
@@ -53,7 +53,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         )
         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"))
 
         grid0.addWidget(self.painttooldia_entry, 0, 1)
@@ -241,8 +241,8 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         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(
             _("If checked, use 'rest machining'.\n"
               "Basically it will clear copper outside PCB features,\n"
@@ -277,7 +277,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         # )
         self.selectmethod_combo = FCComboBox()
         self.selectmethod_combo.addItems(
-            [_("Polygon Selection"), _("Area Selection"), _("All Polygons"), _("Reference Object")]
+            [_("Polygon Selection"), _("Area Selection"), _("All"), _("Reference Object")]
         )
 
         grid0.addWidget(selectlabel, 15, 0)
@@ -302,10 +302,10 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         # ## Plotting type
         self.paint_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
                                               {"label": _("Progressive"), "value": "progressive"}])
-        plotting_label = QtWidgets.QLabel('%s:' % _("Paint Plotting"))
+        plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
         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(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.ToolsPanelizePrefGroupUI import ToolsPanelizePrefGroupUI
 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.ToolsCutoutPrefGroupUI import ToolsCutoutPrefGroupUI
 from AppGUI.preferences.tools.ToolsNCCPrefGroupUI import ToolsNCCPrefGroupUI
+from AppGUI.preferences.tools.ToolsPaintPrefGroupUI import ToolsPaintPrefGroupUI
+from AppGUI.preferences.tools.ToolsISOPrefGroupUI import ToolsISOPrefGroupUI
 
 import gettext
 import AppTranslation as fcTranslate
@@ -36,6 +38,9 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
         self.setLayout(self.layout)
         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.setMinimumWidth(220)
 
@@ -75,7 +80,7 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
 
         self.vlay1 = QtWidgets.QVBoxLayout()
         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.addWidget(self.tools_transform_group)
@@ -89,6 +94,7 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
         self.vlay4 = QtWidgets.QVBoxLayout()
         self.vlay4.addWidget(self.tools_solderpaste_group)
         self.vlay4.addWidget(self.tools_corners_group)
+        self.vlay4.addWidget(self.tools_panelize_group)
 
         self.layout.addLayout(self.vlay)
         self.layout.addLayout(self.vlay1)

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

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 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
 
 import gettext
@@ -45,7 +45,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
               "The value of the diameter has to use the dot decimals separator.\n"
               "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(self.nozzle_tool_dia_entry, 0, 1)
@@ -130,7 +130,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.z_toolchange_entry, 6, 1)
 
         # 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.setToolTip(
             _("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.QtCore import QSettings
 
-from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCEntry
+from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 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 '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_entry, 15, 0, 1, 2)

+ 2 - 2
AppObjects/FlatCAMCNCJob.py

@@ -506,10 +506,10 @@ class CNCJobObject(FlatCAMObj, CNCjob):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export Machine Code ..."),
                 directory=dir_file_to_save,
-                filter=_filter_
+                ext_filter=_filter_
             )
         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)
 

+ 85 - 15
AppObjects/FlatCAMExcellon.py

@@ -19,6 +19,7 @@ from AppParsers.ParseExcellon import Excellon
 from AppObjects.FlatCAMObj import *
 
 import itertools
+import numpy as np
 
 import gettext
 import AppTranslation as fcTranslate
@@ -50,6 +51,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.options.update({
             "plot": True,
             "solid": False,
+            "multicolored": False,
 
             "operation": "drill",
             "milling_type": "drills",
@@ -607,6 +609,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.form_fields.update({
             "plot": self.ui.plot_cb,
             "solid": self.ui.solid_cb,
+            "multicolored": self.ui.multicolored_cb,
 
             "operation": self.ui.operation_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.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_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)
@@ -1180,13 +1185,25 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
     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
         it takes it's options from parameters or otherwise from the
         object's options and returns a (success, msg) tuple as feedback
         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
@@ -1250,7 +1267,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options["cnctooldia"] = str(tooldia)
-
+            geo_obj.options["multidepth"] = self.options["multidepth"]
             geo_obj.solid_geometry = []
 
             # 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, ""
 
-    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
         it takes it's options from parameters or otherwise from the
         object's options and returns a (success, msg) tuple as feedback
         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
@@ -1341,7 +1370,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options["cnctooldia"] = str(tooldia)
-
+            geo_obj.options["multidepth"] = self.options["multidepth"]
             geo_obj.solid_geometry = []
 
             # 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.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):
         self.app.defaults.report_usage("excellon_on_create_milling_slots_button")
         self.read_form()
 
-        self.generate_milling_slots(use_thread=False)
+        self.generate_milling_slots(use_thread=False, plot=True)
 
     def on_pp_changed(self):
         current_pp = self.ui.pp_excellon_name_cb.get_value()
@@ -1727,6 +1756,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.read_form_item('solid')
         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):
         if self.muted_ui:
             return
@@ -1800,6 +1835,27 @@ class ExcellonObject(FlatCAMObj, Excellon):
         if not FlatCAMObj.plot(self):
             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:
         #     # Plot Excellon (All polygons?)
         #     if self.options["solid"]:
@@ -1831,12 +1887,26 @@ class ExcellonObject(FlatCAMObj, Excellon):
         try:
             # Plot Excellon (All polygons?)
             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:
                 for geo in self.solid_geometry:
                     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({
             "plot": True,
+            "multicolored": False,
             "cutz": -0.002,
             "vtipdia": 0.1,
             "vtipangle": 30,
@@ -396,6 +397,7 @@ class GeometryObject(FlatCAMObj, Geometry):
 
         self.form_fields.update({
             "plot": self.ui.plot_cb,
+            "multicolored": self.ui.multicolored_cb,
             "cutz": self.ui.cutz_entry,
             "vtipdia": self.ui.tipdia_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.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.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))
@@ -2665,6 +2669,27 @@ class GeometryObject(FlatCAMObj, Geometry):
         if not FlatCAMObj.plot(self):
             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:
             # plot solid geometries found as members of self.tools attribute dict
             # for MultiGeo
@@ -2672,7 +2697,8 @@ class GeometryObject(FlatCAMObj, Geometry):
                 for tooluid_key in self.tools:
                     solid_geometry = self.tools[tooluid_key]['solid_geometry']
                     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:
                 # plot solid geometry that may be an direct attribute of the geometry object
                 # for SingleGeo
@@ -2740,6 +2766,12 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.ui.plot_cb.setChecked(True)
         self.ui_connect()
 
+    def on_multicolored_cb_click(self, *args):
+        if self.muted_ui:
+            return
+        self.read_form_item('multicolored')
+        self.plot()
+
     @staticmethod
     def merge(geo_list, geo_final, multigeo=None):
         """

+ 18 - 680
AppObjects/FlatCAMGerber.py

@@ -115,23 +115,12 @@ class GerberObject(FlatCAMObj, Gerber):
             "plot": True,
             "multicolored": 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,
             "noncopperrounded": False,
             "bboxmargin": 0.0,
             "bboxrounded": False,
             "aperture_display": False,
             "follow": False,
-            "iso_scope": 'all',
-            "iso_type": 'full'
         })
 
         # 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,
             "multicolored": self.ui.multicolored_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,
             "noncopperrounded": self.ui.noncopper_rounded_cb,
             "bboxmargin": self.ui.bbmargin_entry,
             "bboxrounded": self.ui.bbrounded_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
         self.to_form()
 
         assert isinstance(self.ui, GerberObjectUI)
+
         self.ui.plot_cb.stateChanged.connect(self.on_plot_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_iso_button.clicked.connect(self.on_iso_button_click)
 
         # Tools
         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.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
         if self.app.defaults["global_app_level"] == 'b':
             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.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.except_cb.setChecked(False)
-            self.ui.except_cb.hide()
+
         else:
             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':
             self.ui.create_buffer_button.show()
@@ -300,58 +241,6 @@ class GerberObject(FlatCAMObj, Gerber):
         self.build_ui()
         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):
         FlatCAMObj.build_ui(self)
 
@@ -562,521 +451,6 @@ class GerberObject(FlatCAMObj, Gerber):
 
         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):
         # 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
@@ -1117,64 +491,28 @@ class GerberObject(FlatCAMObj, Gerber):
                 return 'fail'
         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:
-            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:
-            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):
         if self.muted_ui:

+ 13 - 13
AppObjects/FlatCAMObj.py

@@ -459,20 +459,20 @@ class FlatCAMObj(QtCore.QObject):
     def visible(self, value, threaded=True):
         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:
-            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
     def drawing_tolerance(self):

+ 3 - 3
AppObjects/ObjectCollection.py

@@ -1185,7 +1185,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                 except Exception:
                     pass
             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:
             self.set_all_inactive()
             for act in self.app.ui.menuobjects.actions():
@@ -1195,6 +1195,6 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                     pass
 
             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:
-                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):
         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:
-            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):
         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:
-            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):
         """

+ 14 - 6
AppTools/ToolCutOut.py

@@ -501,6 +501,13 @@ class CutOut(AppTool):
             "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
             "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
             "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):
@@ -912,7 +919,7 @@ class CutOut(AppTool):
         if 0 in {self.cutting_dia}:
             self.app.inform.emit('[ERROR_NOTCL] %s' %
                                  _("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())
 
@@ -923,7 +930,7 @@ class CutOut(AppTool):
         except Exception as e:
             log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
             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:
             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,
         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
         :return: none
@@ -1367,6 +1374,7 @@ def flatten(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
     :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 copy import deepcopy
+import math
 
 import logging
 import gettext
@@ -95,7 +96,7 @@ class ToolEtchCompensation(AppTool):
 
         hlay_1 = QtWidgets.QHBoxLayout()
 
-        self.oz_entry = NumericalEvalEntry()
+        self.oz_entry = NumericalEvalEntry(border_color='#0069A9')
         self.oz_entry.setPlaceholderText(_("Oz value"))
         self.oz_to_um_entry = FCEntry()
         self.oz_to_um_entry.setPlaceholderText(_("Microns value"))
@@ -116,7 +117,7 @@ class ToolEtchCompensation(AppTool):
 
         hlay_2 = QtWidgets.QHBoxLayout()
 
-        self.mils_entry = NumericalEvalEntry()
+        self.mils_entry = NumericalEvalEntry(border_color='#0069A9')
         self.mils_entry.setPlaceholderText(_("Mils value"))
         self.mils_to_um_entry = FCEntry()
         self.mils_to_um_entry.setPlaceholderText(_("Microns value"))
@@ -155,8 +156,9 @@ class ToolEtchCompensation(AppTool):
               "- preselection -> value which depends on a selection of etchants")
         )
         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)
 
         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"
               "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.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
         self.etchants_label.hide()
         self.etchants_combo.hide()
         self.factor_label.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.setFrameShape(QtWidgets.QFrame.HLine)
@@ -265,7 +283,7 @@ class ToolEtchCompensation(AppTool):
 
     def set_tool_ui(self):
         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):
         """
@@ -276,16 +294,27 @@ class ToolEtchCompensation(AppTool):
         :return:        None
         :rtype:
         """
-        if val == 'c':
+        if val == 'factor':
             self.etchants_label.hide()
             self.etchants_combo.hide()
             self.factor_label.show()
             self.factor_entry.show()
-        else:
+            self.offset_label.hide()
+            self.offset_entry.hide()
+        elif val == 'etch_list':
             self.etchants_label.show()
             self.etchants_combo.show()
             self.factor_label.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):
         try:
@@ -294,6 +323,7 @@ class ToolEtchCompensation(AppTool):
             # mils to microns by multiplying with 25.4
             val *= 34.798
         except Exception:
+            self.oz_to_um_entry.set_value('')
             return
         self.oz_to_um_entry.set_value(val, self.decimals)
 
@@ -302,10 +332,13 @@ class ToolEtchCompensation(AppTool):
             val = eval(txt)
             val *= 25.4
         except Exception:
+            self.mils_to_um_entry.set_value('')
             return
         self.mils_to_um_entry.set_value(val, self.decimals)
 
     def on_compensate(self):
+        log.debug("ToolEtchCompensation.on_compensate()")
+
         ratio_type = self.ratio_radio.get_value()
         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)))
             return
 
-        if ratio_type == 'c':
+        if ratio_type == 'factor':
             etch_factor = 1 / self.factor_entry.get_value()
-        else:
+            offset = thickness / etch_factor
+        elif ratio_type == 'etch_list':
             etchant = self.etchants_combo.get_value()
             if etchant == "CuCl2":
                 etch_factor = 0.33
             else:
                 etch_factor = 0.25
-        offset = thickness / etch_factor
+            offset = thickness / etch_factor
+        else:
+            offset = self.offset_entry.get_value() / 1000   # in microns
 
         try:
             __ = iter(grb_obj.solid_geometry)
@@ -354,13 +390,32 @@ class ToolEtchCompensation(AppTool):
 
         new_apertures = deepcopy(grb_obj.apertures)
 
+        # update the apertures attributes (keys in the apertures dict)
         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':
-                    for geo_el in new_apertures[ap]['geometry']:
+                    for geo_el in new_apertures[ap][k]:
                         if 'solid' in geo_el:
                             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):
             """
             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(
                 caption=_("Export positive film"),
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
-                filter=filter_ext)
+                ext_filter=filter_ext)
         except TypeError:
             filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export positive film"))
 
@@ -875,7 +875,7 @@ class Film(AppTool):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export negative film"),
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
-                filter=filter_ext)
+                ext_filter=filter_ext)
         except TypeError:
             filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export negative film"))
 

Plik diff jest za duży
+ 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)
 
         # 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.setToolTip(
@@ -1621,10 +1621,12 @@ class NonCopperClear(AppTool, Gerber):
                                                                     "use a number."))
                         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:
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
             return

+ 3 - 3
AppTools/ToolPaint.py

@@ -438,7 +438,7 @@ class ToolPaint(AppTool, Gerber):
         )
         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.setToolTip(
             _("If checked, use 'rest machining'.\n"
@@ -482,7 +482,7 @@ class ToolPaint(AppTool, Gerber):
 
         self.selectmethod_combo = FCComboBox()
         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')
 
@@ -1423,7 +1423,7 @@ class ToolPaint(AppTool, Gerber):
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
             return
 
-        if self.select_method == _("All Polygons"):
+        if self.select_method == _("All"):
             self.paint_poly_all(self.paint_obj,
                                 tooldia=self.tooldia_list,
                                 outname=self.o_name)

+ 4 - 4
AppTools/ToolQRCode.py

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

+ 7 - 7
AppTools/ToolRulesCheck.py

@@ -951,7 +951,7 @@ class RulesCheck(AppTool):
                                 geo = geo_el['solid']
                                 pt = geo.representative_point()
                                 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
                     # not have the size key
                     pass
@@ -1137,7 +1137,7 @@ class RulesCheck(AppTool):
                     copper_list.append(elem_dict)
 
                 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['name'] = deepcopy(copper_name_2)
                     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)
 
                 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['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures)
 
                 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['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():
                     solder_obj = self.sm_t_object.currentText()
-                    if solder_obj !=  '':
+                    if solder_obj != '':
                         sm_dict['name'] = deepcopy(solder_obj)
                         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"))))
                 if self.sm_b_cb.get_value():
                     solder_obj = self.sm_b_object.currentText()
-                    if solder_obj !=  '':
+                    if solder_obj != '':
                         sm_dict['name'] = deepcopy(solder_obj)
                         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.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):
         # 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.tcl_commands_storage = {}
+        self.tcl = None
 
         self.init_tcl()
 
@@ -345,11 +346,11 @@ class FCShell(TermWidget):
 
     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
         # 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.
         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')
@@ -404,12 +405,11 @@ class FCShell(TermWidget):
                 self.append_output(result + '\n')
 
         except tk.TclError as e:
-
             # This will display more precise answer if something in TCL shell fails
             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:
-                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
             if reraise:
                 raise e

+ 3 - 2
AppTools/ToolSolderPaste.py

@@ -1493,10 +1493,11 @@ class SolderPaste(AppTool):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export GCode ..."),
                 directory=dir_file_to_save,
-                filter=_filter_
+                ext_filter=_filter_
             )
         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 == '':
             self.app.inform.emit('[WARNING_NOTCL] %s' %

+ 130 - 131
AppTools/ToolSub.py

@@ -32,6 +32,11 @@ class ToolSub(AppTool):
 
     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")
 
     def __init__(self, app):
@@ -218,18 +223,14 @@ class ToolSub(AppTool):
 
         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.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)
 
     def install(self, icon=None, separator=None, **kwargs):
@@ -310,138 +311,141 @@ class ToolSub(AppTool):
         # crate the new_apertures dict structure
         for apid in self.target_grb_obj.apertures:
             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 = []
             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:
                 poly_buff = work_poly_buff.buffer(0.0000001)
             except ValueError:
@@ -454,25 +458,22 @@ class ToolSub(AppTool):
 
             grb_obj.solid_geometry = deepcopy(poly_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 ...")):
             ret = self.app.app_obj.new_object('gerber', outname, obj_init, autoselected=False)
             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
 
             # GUI feedback
-            self.app.inform.emit('[success] %s: %s' %
-                                 (_("Created"), outname))
+            self.app.inform.emit('[success] %s: %s' % (_("Created"), outname))
 
             # cleanup
             self.new_apertures.clear()
             self.new_solid_geometry[:] = []
-            try:
-                self.sub_union[:] = []
-            except TypeError:
-                self.sub_union = []
+            self.results = []
 
     def on_geo_intersection_click(self):
         # reset previous values
@@ -739,11 +740,9 @@ class ToolSub(AppTool):
                 outname = self.target_geo_combo.currentText() + '_sub'
 
                 # 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:
-            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):
         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()))
         msgbox.setWindowTitle(_("Apply Language ..."))
         msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/language32.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         bt_yes = msgbox.addButton(_("Yes"), QtWidgets.QMessageBox.YesRole)
         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?"))
         msgbox.setWindowTitle(_("Save changes"))
         msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/save_as.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
         bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
 

+ 131 - 162
App_Main.py

@@ -204,7 +204,7 @@ class App(QtCore.QObject):
     # Inform the user
     # Handled by:
     #  * App.info() --> Print on the status bar
-    inform = QtCore.pyqtSignal(str)
+    inform = QtCore.pyqtSignal([str], [str, bool])
 
     app_quit = QtCore.pyqtSignal()
 
@@ -757,7 +757,9 @@ class App(QtCore.QObject):
 
         # ########################################## Custom signals  ################################################
         # 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
         self.app_quit.connect(self.quit_application, type=Qt.QueuedConnection)
         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.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_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_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)
 
@@ -1019,6 +1022,15 @@ class App(QtCore.QObject):
         self.ui.util_defaults_form.kw_group.del_btn.clicked.connect(
             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
         self.proc_container.idle_flag.connect(self.app_is_idle)
 
@@ -1343,7 +1355,7 @@ class App(QtCore.QObject):
         try:
             self.install_tools()
         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 ###########################################
@@ -1439,12 +1451,6 @@ class App(QtCore.QObject):
         # holds the key modifier if pressed (CTRL, SHIFT or ALT)
         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
         self.toggle_codeeditor = False
 
@@ -2206,6 +2212,7 @@ class App(QtCore.QObject):
                 msgbox.setText(_("Do you want to save the edited object?"))
                 msgbox.setWindowTitle(_("Close Editor"))
                 msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png'))
+                msgbox.setIcon(QtWidgets.QMessageBox.Question)
 
                 bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
                 bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
@@ -2242,6 +2249,7 @@ class App(QtCore.QObject):
                             log.debug("App.editor2object() --> Geometry --> %s" % str(e))
 
                         edited_obj.build_ui()
+                        edited_obj.plot()
                         self.inform.emit('[success] %s' % _("Editor exited. Editor content saved."))
 
                     elif isinstance(edited_obj, GerberObject):
@@ -2298,6 +2306,7 @@ class App(QtCore.QObject):
                     if isinstance(edited_obj, GeometryObject):
                         self.geo_editor.deactivate()
                         edited_obj.build_ui()
+                        edited_obj.plot()
                     elif isinstance(edited_obj, GerberObject):
                         self.grb_editor.deactivate_grb_editor()
                         edited_obj.build_ui()
@@ -2361,12 +2370,17 @@ class App(QtCore.QObject):
             loc = os.path.dirname(__file__)
         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
         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
         """
 
@@ -2377,32 +2391,33 @@ class App(QtCore.QObject):
             msg_ = match.group(2)
             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:
             self.ui.fcinfo.set_status(str(msg), level="info")
 
             # make sure that if the message is to clear the infobar with a space
             # is not printed over and over on the shell
-            if msg != '':
+            if msg != '' and shell_echo is True:
                 self.shell_message(msg)
 
     def on_import_preferences(self):
@@ -2456,10 +2471,11 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export FlatCAM Preferences"),
                 directory=self.data_path + '/preferences_' + date,
-                filter=filter__
+                ext_filter=filter__
             )
         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)
         if filename == "":
             self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
@@ -2501,10 +2517,10 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Save to file"),
                 directory=path_to_save + '/file_' + self.date,
-                filter=filter__
+                ext_filter=filter__
             )
         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)
 
@@ -3092,6 +3108,8 @@ class App(QtCore.QObject):
 
         msgbox.setWindowTitle(_("Alternative website"))
         msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/globe16.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         bt_yes = msgbox.addButton(_('Close'), QtWidgets.QMessageBox.YesRole)
 
         msgbox.setDefaultButton(bt_yes)
@@ -3855,9 +3873,10 @@ class App(QtCore.QObject):
             return
 
         # 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",
 
                       'excellon_cutz', 'excellon_travelz', "excellon_toolchangexy", 'excellon_offset',
@@ -4000,6 +4019,8 @@ class App(QtCore.QObject):
         msgbox = QtWidgets.QMessageBox()
         msgbox.setWindowTitle(_("Toggle Units"))
         msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/toggle_units32.png'))
+        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
         msgbox.setText(_("Changing the units of the project\n"
                          "will scale all objects.\n\n"
                          "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_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):
         self.ui.notebook.set_detachable(val=checked)
         self.defaults["global_tabs_detachable"] = checked
@@ -4206,10 +4138,10 @@ class App(QtCore.QObject):
     def on_workspace(self):
         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.inform.emit(_("Workspace enabled."))
+            self.inform[str, bool].emit(_("Workspace enabled."), False)
         else:
             self.plotcanvas.delete_workspace()
-            self.inform.emit(_("Workspace disabled."))
+            self.inform[str, bool].emit(_("Workspace disabled."), False)
         self.preferencesUiManager.defaults_read_form()
         # self.save_defaults(silent=True)
 
@@ -4277,6 +4209,8 @@ class App(QtCore.QObject):
                                      "Go to Preferences -> General - Show Advanced Options."))
                     msgbox.setWindowTitle("Tool adding ...")
                     msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/warning.png'))
+                    msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
                     bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
 
                     msgbox.setDefaultButton(bt_ok)
@@ -4323,6 +4257,10 @@ class App(QtCore.QObject):
             # and only if the tool is Solder Paste Dispensing Tool
             elif tool_widget == self.paste_tool.toolName:
                 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:
             self.on_delete()
 
@@ -4351,6 +4289,8 @@ class App(QtCore.QObject):
                 msgbox = QtWidgets.QMessageBox()
                 msgbox.setWindowTitle(_("Delete objects"))
                 msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/deleteshape32.png'))
+                msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
                 # msgbox.setText("<B>%s</B>" % _("Change project units ..."))
                 msgbox.setText(_("Are you sure you want to permanently delete\n"
                                  "the selected objects?"))
@@ -4376,7 +4316,7 @@ class App(QtCore.QObject):
                                 obj_active.mark_shapes[el].enabled = False
                                 # obj_active.mark_shapes[el] = None
                                 del el
-                        elif isinstance(obj_active, CNCJobObject):
+                        elif obj_active.kind == 'cncjob':
                             try:
                                 obj_active.text_col.enabled = False
                                 del obj_active.text_col
@@ -4390,13 +4330,13 @@ class App(QtCore.QObject):
                     while self.collection.get_selected():
                         self.delete_first_selected()
 
-                    self.inform.emit('%s...' % _("Object(s) deleted"))
                     # make sure that the selection shape is deleted, too
                     self.delete_selection_shape()
 
                     # if there are no longer objects delete also the exclusion areas shapes
                     if not self.collection.get_list():
                         self.exc_areas.clear_shapes()
+                    self.inform.emit('%s...' % _("Object(s) deleted"))
                 else:
                     self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No object(s) selected..."))
         else:
@@ -5283,7 +5223,13 @@ class App(QtCore.QObject):
                 callback_on_edited=self.on_tools_db_edited,
                 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
         try:
             self.ui.plot_tab_area.addTab(self.tools_db_tab, _("Tools Database"))
@@ -5310,7 +5256,7 @@ class App(QtCore.QObject):
         :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()):
             if self.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
@@ -5328,9 +5274,19 @@ class App(QtCore.QObject):
         tool_from_db = deepcopy(tool)
 
         obj = self.collection.get_active()
-        if isinstance(obj, GeometryObject):
+        if obj.kind == 'geometry':
             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
             for idx in range(self.ui.plot_tab_area.count()):
                 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?"))
                 msgbox.setWindowTitle(_("Save Tools Database"))
                 msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png'))
+                msgbox.setIcon(QtWidgets.QMessageBox.Question)
 
                 bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
                 msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
@@ -5643,7 +5600,6 @@ class App(QtCore.QObject):
         :return: None
         """
 
-        self.defaults.report_usage("on_toolbar_replot")
         self.log.debug("on_toolbar_replot()")
 
         try:
@@ -6422,6 +6378,8 @@ class App(QtCore.QObject):
                              "Do you want to Save the project?"))
             msgbox.setWindowTitle(_("Save changes"))
             msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png'))
+            msgbox.setIcon(QtWidgets.QMessageBox.Question)
+
             bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
             bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
             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."))
             msg = _("Please Select a Geometry object to export")
             msgbox = QtWidgets.QMessageBox()
+            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
             msgbox.setInformativeText(msg)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             msgbox.setDefaultButton(bt_ok)
@@ -6821,6 +6781,8 @@ class App(QtCore.QObject):
             msg = '[ERROR_NOTCL] %s' % \
                   _("Only Geometry, Gerber and CNCJob objects can be used.")
             msgbox = QtWidgets.QMessageBox()
+            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
             msgbox.setInformativeText(msg)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             msgbox.setDefaultButton(bt_ok)
@@ -6834,9 +6796,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export SVG"),
                 directory=self.get_last_save_folder() + '/' + str(name) + '_svg',
-                filter=_filter)
+                ext_filter=_filter)
         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)
 
@@ -6857,8 +6819,9 @@ class App(QtCore.QObject):
         self.date = ''.join(c for c in self.date if c not in ':-')
         self.date = self.date.replace(' ', '_')
 
+        data = None
         if self.is_legacy is False:
-            image = _screenshot()
+            image = _screenshot(alpha=None)
             data = np.asarray(image)
             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'))
@@ -6869,9 +6832,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export PNG Image"),
                 directory=self.get_last_save_folder() + '/png_' + self.date,
-                filter=filter_)
+                ext_filter=filter_)
         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)
 
@@ -6914,9 +6877,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption="Save Gerber source file",
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         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)
 
@@ -6955,9 +6918,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption="Save Script source file",
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         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)
 
@@ -6996,9 +6959,10 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption="Save Document source file",
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         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)
 
@@ -7037,9 +7001,10 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Save Excellon source file"),
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         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)
 
@@ -7078,9 +7043,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export Excellon"),
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter)
+                ext_filter=_filter)
         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)
 
@@ -7122,9 +7087,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export Gerber"),
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter_)
+                ext_filter=_filter_)
         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)
 
@@ -7154,6 +7119,8 @@ class App(QtCore.QObject):
             self.inform.emit('[WARNING_NOTCL] %s' % _("No object selected."))
             msg = _("Please Select a Geometry object to export")
             msgbox = QtWidgets.QMessageBox()
+            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
             msgbox.setInformativeText(msg)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             msgbox.setDefaultButton(bt_ok)
@@ -7164,6 +7131,8 @@ class App(QtCore.QObject):
         if not isinstance(obj, GeometryObject):
             msg = '[ERROR_NOTCL] %s' % _("Only Geometry objects can be used.")
             msgbox = QtWidgets.QMessageBox()
+            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
+
             msgbox.setInformativeText(msg)
             bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
             msgbox.setDefaultButton(bt_ok)
@@ -7178,9 +7147,9 @@ class App(QtCore.QObject):
             filename, _f = FCFileSaveDialog.get_saved_filename(
                 caption=_("Export DXF"),
                 directory=self.get_last_save_folder() + '/' + name,
-                filter=_filter_)
+                ext_filter=_filter_)
         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)
 
@@ -7630,10 +7599,10 @@ class App(QtCore.QObject):
                 caption=_("Save Project As ..."),
                 directory='{l_save}/{proj}_{date}'.format(l_save=str(self.get_last_save_folder()), date=self.date,
                                                           proj=_("Project")),
-                filter=filter_
+                ext_filter=filter_
             )
         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)
 
@@ -7685,10 +7654,10 @@ class App(QtCore.QObject):
                 directory='{l_save}/{obj_name}_{date}'.format(l_save=str(self.get_last_save_folder()),
                                                               obj_name=obj_name,
                                                               date=self.date),
-                filter=filter_
+                ext_filter=filter_
             )
         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)
 
@@ -9892,12 +9861,12 @@ class App(QtCore.QObject):
         """
         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
         """
         if show:

+ 1 - 1
Bookmark.py

@@ -292,7 +292,7 @@ class BookmarkManager(QtWidgets.QWidget):
                                                                 l_save=str(self.app.get_last_save_folder()),
                                                                 n=_("Bookmarks"),
                                                                 date=date),
-                                                           filter=filter__)
+                                                           ext_filter=filter__)
 
         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
 
 - working on Isolation Tool: made to work the tool parameters data to GUI and GUI to data
 - 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
 - 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
 

+ 2 - 2
Common.py

@@ -572,7 +572,7 @@ class ExclusionAreas(QtCore.QObject):
         AppTool.delete_moving_selection_shape(self)
         self.app.delete_selection_shape()
         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):
         """
@@ -621,7 +621,7 @@ class ExclusionAreas(QtCore.QObject):
                                             """)
             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):
         """

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.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.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,
                                                                tolerance,
-                                                               z_move=z_move,_postproc=p,
+                                                               z_move=z_move, postproc=p,
                                                                old_point=current_pt)
 
                 # calculate the travel distance
@@ -4831,15 +4832,25 @@ class CNCjob(Geometry):
         # Current path: temporary storage until tool is
         # lifted or lowered.
         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)
             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:
-            if self.app.defaults["geometry_toolchangexy"] == '':
+            if self.app.defaults["geometry_toolchangexy"] == '' or self.app.defaults["geometry_toolchangexy"] is None:
                 pos_xy = (0, 0)
             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 = [(0, 0)]

+ 26 - 13
defaults.py

@@ -172,12 +172,6 @@ class FlatCAMDefaults:
                                "All Files (*.*)",
 
         # 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_noncopperrounded": False,
         "gerber_bboxmargin": 0.1,
@@ -188,11 +182,6 @@ class FlatCAMDefaults:
         "gerber_aperture_scale_factor": 1.0,
         "gerber_aperture_buffer_factor": 0.0,
         "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_simplification": False,
         "gerber_simp_tolerance": 0.0005,
@@ -223,6 +212,7 @@ class FlatCAMDefaults:
         # Excellon General
         "excellon_plot": True,
         "excellon_solid": True,
+        "excellon_multicolored": False,
         "excellon_format_upper_in": 2,
         "excellon_format_lower_in": 4,
         "excellon_format_upper_mm": 3,
@@ -311,6 +301,7 @@ class FlatCAMDefaults:
 
         # Geometry General
         "geometry_plot": True,
+        "geometry_multicolored": False,
         "geometry_circle_steps": 64,
         "geometry_cnctooldia": "2.4",
         "geometry_plot_line": "#FF0000",
@@ -391,6 +382,28 @@ class FlatCAMDefaults:
         "cncjob_annotation_fontsize": 9,
         "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
         "tools_ncctools": "1.0, 0.5",
         "tools_nccorder": 'rev',
@@ -405,13 +418,13 @@ class FlatCAMDefaults:
         "tools_ncc_offset_value": 0.0000,
         "tools_nccref": _('Itself'),
         "tools_ncc_area_shape": "square",
-        "tools_ncc_plotting": 'normal',
         "tools_nccmilling_type": 'cl',
         "tools_ncctool_type": 'C1',
         "tools_ncccutz": -0.05,
         "tools_ncctipdia": 0.1,
         "tools_ncctipangle": 30,
         "tools_nccnewdia": 0.1,
+        "tools_ncc_plotting": 'normal',
 
         # Cutout Tool
         "tools_cutouttooldia": 2.4,
@@ -430,7 +443,7 @@ class FlatCAMDefaults:
         "tools_paintoverlap": 20,
         "tools_paintmargin": 0.0,
         "tools_paintmethod": _("Seed"),
-        "tools_selectmethod": _("All Polygons"),
+        "tools_selectmethod": _("All"),
         "tools_paint_area_shape": "square",
         "tools_pathconnect": True,
         "tools_paintcontour": True,

+ 1 - 1
tclCommands/TclCommandIsolate.py

@@ -89,7 +89,7 @@ class TclCommandIsolate(TclCommandSignaled):
                 par = args['combine']
             args['combine'] = bool(eval(par))
         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)
         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
         if "all" in args:
-            select = _("All Polygons")
+            select = _("All")
         elif "single" in args:
             select = _("Polygon Selection")
         else:

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików