Marius Stanciu 5 rokov pred
rodič
commit
f5c84fdc49
100 zmenil súbory, kde vykonal 4791 pridanie a 3462 odobranie
  1. 224 18
      AppDatabase.py
  2. 29 48
      AppEditors/FlatCAMExcEditor.py
  3. 67 71
      AppEditors/FlatCAMGeoEditor.py
  4. 119 125
      AppEditors/FlatCAMGrbEditor.py
  5. 4 4
      AppEditors/FlatCAMTextEditor.py
  6. 0 0
      AppEditors/__init__.py
  7. 181 0
      AppGUI/ColumnarFlowLayout.py
  8. 391 14
      AppGUI/GUIElements.py
  9. 142 899
      AppGUI/MainGUI.py
  10. 151 351
      AppGUI/ObjectUI.py
  11. 152 14
      AppGUI/PlotCanvas.py
  12. 233 9
      AppGUI/PlotCanvasLegacy.py
  13. 1 0
      AppGUI/VisPyCanvas.py
  14. 0 0
      AppGUI/VisPyData/data/fonts/opensans-regular.ttf
  15. 0 0
      AppGUI/VisPyData/data/freetype/freetype253.dll
  16. 0 0
      AppGUI/VisPyData/data/freetype/freetype253_x64.dll
  17. 0 0
      AppGUI/VisPyPatches.py
  18. 0 0
      AppGUI/VisPyTesselators.py
  19. 1 1
      AppGUI/VisPyVisuals.py
  20. 0 0
      AppGUI/__init__.py
  21. 327 0
      AppGUI/preferences/OptionUI.py
  22. 77 0
      AppGUI/preferences/OptionsGroupUI.py
  23. 41 0
      AppGUI/preferences/PreferencesSectionUI.py
  24. 184 218
      AppGUI/preferences/PreferencesUIManager.py
  25. 2 2
      AppGUI/preferences/__init__.py
  26. 8 37
      AppGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py
  27. 23 142
      AppGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py
  28. 3 3
      AppGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py
  29. 3 3
      AppGUI/preferences/cncjob/CNCJobPreferencesUI.py
  30. 0 0
      AppGUI/preferences/cncjob/__init__.py
  31. 5 5
      AppGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py
  32. 3 3
      AppGUI/preferences/excellon/ExcellonEditorPrefGroupUI.py
  33. 3 3
      AppGUI/preferences/excellon/ExcellonExpPrefGroupUI.py
  34. 137 86
      AppGUI/preferences/excellon/ExcellonGenPrefGroupUI.py
  35. 6 6
      AppGUI/preferences/excellon/ExcellonOptPrefGroupUI.py
  36. 7 7
      AppGUI/preferences/excellon/ExcellonPreferencesUI.py
  37. 0 0
      AppGUI/preferences/excellon/__init__.py
  38. 32 50
      AppGUI/preferences/general/GeneralAPPSetGroupUI.py
  39. 5 6
      AppGUI/preferences/general/GeneralAppPrefGroupUI.py
  40. 302 0
      AppGUI/preferences/general/GeneralAppSettingsGroupUI.py
  41. 73 303
      AppGUI/preferences/general/GeneralGUIPrefGroupUI.py
  42. 4 4
      AppGUI/preferences/general/GeneralPreferencesUI.py
  43. 0 0
      AppGUI/preferences/general/__init__.py
  44. 20 12
      AppGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py
  45. 3 3
      AppGUI/preferences/geometry/GeometryEditorPrefGroupUI.py
  46. 18 32
      AppGUI/preferences/geometry/GeometryGenPrefGroupUI.py
  47. 6 5
      AppGUI/preferences/geometry/GeometryOptPrefGroupUI.py
  48. 5 5
      AppGUI/preferences/geometry/GeometryPreferencesUI.py
  49. 0 0
      AppGUI/preferences/geometry/__init__.py
  50. 3 82
      AppGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py
  51. 5 4
      AppGUI/preferences/gerber/GerberEditorPrefGroupUI.py
  52. 3 3
      AppGUI/preferences/gerber/GerberExpPrefGroupUI.py
  53. 38 104
      AppGUI/preferences/gerber/GerberGenPrefGroupUI.py
  54. 100 0
      AppGUI/preferences/gerber/GerberOptPrefGroupUI.py
  55. 7 6
      AppGUI/preferences/gerber/GerberPreferencesUI.py
  56. 0 0
      AppGUI/preferences/gerber/__init__.py
  57. 3 3
      AppGUI/preferences/tools/Tools2CThievingPrefGroupUI.py
  58. 4 4
      AppGUI/preferences/tools/Tools2CalPrefGroupUI.py
  59. 3 3
      AppGUI/preferences/tools/Tools2EDrillsPrefGroupUI.py
  60. 3 3
      AppGUI/preferences/tools/Tools2FiducialsPrefGroupUI.py
  61. 3 3
      AppGUI/preferences/tools/Tools2InvertPrefGroupUI.py
  62. 3 3
      AppGUI/preferences/tools/Tools2OptimalPrefGroupUI.py
  63. 10 10
      AppGUI/preferences/tools/Tools2PreferencesUI.py
  64. 3 3
      AppGUI/preferences/tools/Tools2PunchGerberPrefGroupUI.py
  65. 18 30
      AppGUI/preferences/tools/Tools2QRCodePrefGroupUI.py
  66. 3 3
      AppGUI/preferences/tools/Tools2RulesCheckPrefGroupUI.py
  67. 3 3
      AppGUI/preferences/tools/Tools2sidedPrefGroupUI.py
  68. 3 3
      AppGUI/preferences/tools/ToolsCalculatorsPrefGroupUI.py
  69. 81 0
      AppGUI/preferences/tools/ToolsCornersPrefGroupUI.py
  70. 4 4
      AppGUI/preferences/tools/ToolsCutoutPrefGroupUI.py
  71. 14 19
      AppGUI/preferences/tools/ToolsFilmPrefGroupUI.py
  72. 320 0
      AppGUI/preferences/tools/ToolsISOPrefGroupUI.py
  73. 12 12
      AppGUI/preferences/tools/ToolsNCCPrefGroupUI.py
  74. 12 12
      AppGUI/preferences/tools/ToolsPaintPrefGroupUI.py
  75. 3 3
      AppGUI/preferences/tools/ToolsPanelizePrefGroupUI.py
  76. 23 12
      AppGUI/preferences/tools/ToolsPreferencesUI.py
  77. 5 5
      AppGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py
  78. 3 3
      AppGUI/preferences/tools/ToolsSubPrefGroupUI.py
  79. 5 5
      AppGUI/preferences/tools/ToolsTransformPrefGroupUI.py
  80. 0 0
      AppGUI/preferences/tools/__init__.py
  81. 3 3
      AppGUI/preferences/utilities/AutoCompletePrefGroupUI.py
  82. 3 3
      AppGUI/preferences/utilities/FAExcPrefGroupUI.py
  83. 3 3
      AppGUI/preferences/utilities/FAGcoPrefGroupUI.py
  84. 3 3
      AppGUI/preferences/utilities/FAGrbPrefGroupUI.py
  85. 4 4
      AppGUI/preferences/utilities/UtilPreferencesUI.py
  86. 0 0
      AppGUI/preferences/utilities/__init__.py
  87. 394 0
      AppObjects/AppObject.py
  88. 20 17
      AppObjects/FlatCAMCNCJob.py
  89. 24 3
      AppObjects/FlatCAMDocument.py
  90. 182 41
      AppObjects/FlatCAMExcellon.py
  91. 104 59
      AppObjects/FlatCAMGeometry.py
  92. 66 434
      AppObjects/FlatCAMGerber.py
  93. 29 16
      AppObjects/FlatCAMObj.py
  94. 33 7
      AppObjects/FlatCAMScript.py
  95. 216 16
      AppObjects/ObjectCollection.py
  96. 0 0
      AppObjects/__init__.py
  97. 2 2
      AppParsers/ParseDXF.py
  98. 0 0
      AppParsers/ParseDXF_Spline.py
  99. 15 16
      AppParsers/ParseExcellon.py
  100. 1 1
      AppParsers/ParseFont.py

+ 224 - 18
FlatCAMDB.py → AppDatabase.py

@@ -1,5 +1,5 @@
 from PyQt5 import QtGui, QtCore, QtWidgets
-from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
+from AppGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
     FCTree, RadioSet, FCFileSaveDialog
 from camlib import to_dict
 
@@ -8,8 +8,10 @@ import json
 
 from copy import deepcopy
 from datetime import datetime
+import math
+
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -117,7 +119,7 @@ class ToolsDB(QtWidgets.QWidget):
         )
         self.buttons_box.addWidget(import_db_btn)
 
-        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
+        self.add_tool_from_db = FCButton(_("Transfer the Tool"))
         self.add_tool_from_db.setToolTip(
             _("Add a new tool in the Tools Table of the\n"
               "active Geometry object after selecting a tool\n"
@@ -313,7 +315,7 @@ class ToolsDB(QtWidgets.QWidget):
             self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
             return
 
-        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+        self.app.inform.emit('[success] %s: %s' % (_("Loaded Tools DB from"), filename))
 
         self.build_db_ui()
 
@@ -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)
 
@@ -724,7 +726,7 @@ class ToolsDB(QtWidgets.QWidget):
                 self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
                 return
 
-            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+            self.app.inform.emit('[success] %s: %s' % (_("Loaded Tools DB from"), filename))
             self.build_db_ui()
             self.callback_on_edited()
 
@@ -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)
@@ -1478,7 +1497,7 @@ class ToolsDB2(QtWidgets.QWidget):
 
         self.ncc_method_combo = FCComboBox()
         self.ncc_method_combo.addItems(
-            [_("Standard"), _("Seed"), _("Lines")]
+            [_("Standard"), _("Seed"), _("Lines"), _("Combo")]
         )
         self.ncc_method_combo.setObjectName("gdb_n_method")
 
@@ -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
@@ -1678,12 +1792,19 @@ class ToolsDB2(QtWidgets.QWidget):
         )
         self.buttons_box.addWidget(self.save_db_btn)
 
-        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
+        self.add_tool_from_db = FCButton(_("Transfer the Tool"))
         self.add_tool_from_db.setToolTip(
-            _("Add a new tool in the Tools Table of the\n"
-              "active Geometry object after selecting a tool\n"
+            _("Insert a new tool in the Tools Table of the\n"
+              "object/application tool after selecting a tool\n"
               "in the Tools Database.")
         )
+        self.add_tool_from_db.setStyleSheet("""
+                                            QPushButton
+                                            {
+                                                font-weight: bold;
+                                                color: green;
+                                            }
+                                            """)
         self.add_tool_from_db.hide()
 
         self.cancel_tool_from_db = FCButton(_("Cancel"))
@@ -1693,7 +1814,7 @@ class ToolsDB2(QtWidgets.QWidget):
         tree_layout.addLayout(hlay)
         hlay.addWidget(self.add_tool_from_db)
         hlay.addWidget(self.cancel_tool_from_db)
-        hlay.addStretch()
+        # hlay.addStretch()
 
         # ##############################################################################
         # ##############################################################################
@@ -1743,6 +1864,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 +1915,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
@@ -1887,7 +2022,7 @@ class ToolsDB2(QtWidgets.QWidget):
         self.blockSignals(False)
 
     def setup_db_ui(self):
-        filename = self.app.data_path + '/geo_tools_db.FlatDB'
+        filename = self.app.data_path + '\geo_tools_db.FlatDB'
 
         # load the database tools from the file
         try:
@@ -1906,7 +2041,7 @@ class ToolsDB2(QtWidgets.QWidget):
             self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
             return
 
-        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+        self.app.inform.emit('[success] %s: %s' % (_("Loaded Tools DB from"), filename))
 
         self.build_db_ui()
 
@@ -1939,21 +2074,23 @@ class ToolsDB2(QtWidgets.QWidget):
             if self.db_tool_dict:
                 self.storage_to_form(self.db_tool_dict['1'])
 
-                # Enable GUI
+                # Enable AppGUI
                 self.basic_box.setEnabled(True)
                 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()
 
             else:
-                # Disable GUI
+                # Disable AppGUI
                 self.basic_box.setEnabled(False)
                 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,10 +2143,27 @@ 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"],
         })
 
+        temp = []
+        for k, v in self.db_tool_dict.items():
+            if "new_tool_" in v['name']:
+                temp.append(float(v['name'].rpartition('_')[2]))
+
+        if temp:
+            new_name = "new_tool_%d" % int(max(temp) + 1)
+        else:
+            new_name = "new_tool_1"
+
         dict_elem = {}
-        dict_elem['name'] = 'new_tool'
+        dict_elem['name'] = new_name
         if type(self.app.defaults["geometry_cnctooldia"]) == float:
             dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
         else:
@@ -2117,7 +2271,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)
 
@@ -2186,7 +2340,7 @@ class ToolsDB2(QtWidgets.QWidget):
                 self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
                 return
 
-            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
+            self.app.inform.emit('[success] %s: %s' % (_("Loaded Tools DB from"), filename))
             self.build_db_ui()
             self.update_storage()
 
@@ -2218,6 +2372,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 +2413,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 +2592,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):

+ 29 - 48
flatcamEditors/FlatCAMExcEditor.py → AppEditors/FlatCAMExcEditor.py

@@ -9,9 +9,9 @@ from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 
 from camlib import distance, arc, FlatCAMRTreeStorage
-from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
-from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
-from flatcamParsers.ParseExcellon import Excellon
+from AppGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
+from AppEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
+from AppParsers.ParseExcellon import Excellon
 
 from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point
 import shapely.affinity as affinity
@@ -26,7 +26,7 @@ import logging
 from copy import deepcopy
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -2123,7 +2123,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             else:
                 self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
         else:
-            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
             self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_exc_editor')
             self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_exc_editor')
 
@@ -2239,7 +2239,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
-        log.debug("Initialization of the FlatCAM Excellon Editor is finished ...")
+        log.debug("Initialization of the Excellon Editor is finished ...")
 
     def pool_recreated(self, pool):
         self.shapes.pool = pool
@@ -2312,7 +2312,7 @@ class FlatCAMExcEditor(QtCore.QObject):
                 tool_dia = float('%.*f' % (self.decimals, v['C']))
                 self.tool2tooldia[int(k)] = tool_dia
 
-        # Init GUI
+        # Init AppGUI
         self.addtool_entry.set_value(float(self.app.defaults['excellon_editor_newdia']))
         self.drill_array_size_entry.set_value(int(self.app.defaults['excellon_editor_array_size']))
         self.drill_axis_radio.set_value(self.app.defaults['excellon_editor_lin_dir'])
@@ -2819,10 +2819,8 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.tool_shape.enabled = True
         # self.app.app_cursor.enabled = True
 
-        self.app.ui.snap_max_dist_entry.setEnabled(True)
-        self.app.ui.corner_snap_btn.setEnabled(True)
-        self.app.ui.snap_magnet.setVisible(True)
         self.app.ui.corner_snap_btn.setVisible(True)
+        self.app.ui.snap_magnet.setVisible(True)
 
         self.app.ui.exc_editor_menu.setDisabled(False)
         self.app.ui.exc_editor_menu.menuAction().setVisible(True)
@@ -2832,12 +2830,11 @@ 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:
             self.app.ui.grid_snap_btn.trigger()
-            self.app.ui.on_grid_snap_triggered(state=True)
 
         self.app.ui.popmenu_disable.setVisible(False)
         self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
@@ -2869,30 +2866,8 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.clear()
         self.app.ui.exc_edit_toolbar.setDisabled(True)
 
-        settings = QSettings("Open Source", "FlatCAM")
-        if settings.contains("layout"):
-            layout = settings.value('layout', type=str)
-            if layout == 'standard':
-                # self.app.ui.exc_edit_toolbar.setVisible(False)
-
-                self.app.ui.snap_max_dist_entry.setEnabled(False)
-                self.app.ui.corner_snap_btn.setEnabled(False)
-                self.app.ui.snap_magnet.setVisible(False)
-                self.app.ui.corner_snap_btn.setVisible(False)
-            else:
-                # self.app.ui.exc_edit_toolbar.setVisible(True)
-
-                self.app.ui.snap_max_dist_entry.setEnabled(False)
-                self.app.ui.corner_snap_btn.setEnabled(False)
-                self.app.ui.snap_magnet.setVisible(True)
-                self.app.ui.corner_snap_btn.setVisible(True)
-        else:
-            # self.app.ui.exc_edit_toolbar.setVisible(False)
-
-            self.app.ui.snap_max_dist_entry.setEnabled(False)
-            self.app.ui.corner_snap_btn.setEnabled(False)
-            self.app.ui.snap_magnet.setVisible(False)
-            self.app.ui.corner_snap_btn.setVisible(False)
+        self.app.ui.corner_snap_btn.setVisible(False)
+        self.app.ui.snap_magnet.setVisible(False)
 
         # set the Editor Toolbar visibility to what was before entering in the Editor
         self.app.ui.exc_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
@@ -3068,7 +3043,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.set_ui()
 
-        # now that we hava data, create the GUI interface and add it to the Tool Tab
+        # now that we hava data, create the AppGUI interface and add it to the Tool Tab
         self.build_ui(first_run=True)
 
         # we activate this after the initial build as we don't need to see the tool been populated
@@ -3361,15 +3336,17 @@ class FlatCAMExcEditor(QtCore.QObject):
         with self.app.proc_container.new(_("Creating Excellon.")):
 
             try:
-                edited_obj = self.app.new_object("excellon", outname, obj_init)
+                edited_obj = self.app.app_obj.new_object("excellon", outname, obj_init)
                 edited_obj.source_file = self.app.export_excellon(obj_name=edited_obj.options['name'],
                                                                   local_use=edited_obj,
                                                                   filename=None,
                                                                   use_thread=False)
             except Exception as e:
+                self.deactivate()
                 log.error("Error on Edited object creation: %s" % str(e))
                 return
 
+            self.deactivate()
             self.app.inform.emit('[success] %s' % _("Excellon editing finished."))
 
     def on_tool_select(self, tool):
@@ -3463,8 +3440,8 @@ class FlatCAMExcEditor(QtCore.QObject):
             self.pos = (self.pos[0], self.pos[1])
 
         if event.button == 1:
-            self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                                   "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
+            # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+            #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
 
             # Selection with left mouse button
             if self.active_tool is not None and event.button == 1:
@@ -3801,18 +3778,22 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.snap_x = x
         self.snap_y = y
 
-        # update the position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                           "<b>Y</b>: %.4f" % (x, y))
-
         if self.pos is None:
             self.pos = (0, 0)
         self.app.dx = x - self.pos[0]
         self.app.dy = y - self.pos[1]
 
-        # update the reference position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+        # # update the position label in the infobar since the APP mouse event handlers are disconnected
+        self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f&nbsp;" % (x, y))
+        # # update the reference position label in the infobar since the APP mouse event handlers are disconnected
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.app.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, x, units, y, units)
 
         # ## Utility geometry (animated)
         self.update_utility_geometry(data=(x, y))
@@ -4045,7 +4026,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
     def select_tool(self, toolname):
         """
-        Selects a drawing tool. Impacts the object and GUI.
+        Selects a drawing tool. Impacts the object and AppGUI.
 
         :param toolname: Name of the tool.
         :return: None

+ 67 - 71
flatcamEditors/FlatCAMGeoEditor.py → AppEditors/FlatCAMGeoEditor.py

@@ -15,11 +15,10 @@ from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 
 from camlib import distance, arc, three_point_circle, Geometry, FlatCAMRTreeStorage
-from FlatCAMTool import FlatCAMTool
-from flatcamGUI.ObjectUI import RadioSet
-from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
-    FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog, FCTree
-from flatcamParsers.ParseFont import *
+from AppTool import AppTool
+from AppGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
+    FCDoubleSpinner, FCButton, FCInputDialog, FCTree
+from AppParsers.ParseFont import *
 
 from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
 from shapely.ops import cascaded_union, unary_union, linemerge
@@ -34,7 +33,7 @@ from rtree import index as rtindex
 from copy import deepcopy
 # from vispy.io import read_png
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -42,7 +41,7 @@ if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
 
-class BufferSelectionTool(FlatCAMTool):
+class BufferSelectionTool(AppTool):
     """
     Simple input for buffer distance.
     """
@@ -50,7 +49,7 @@ class BufferSelectionTool(FlatCAMTool):
     toolName = "Buffer Selection"
 
     def __init__(self, app, draw_app):
-        FlatCAMTool.__init__(self, app)
+        AppTool.__init__(self, app)
 
         self.draw_app = draw_app
         self.decimals = app.decimals
@@ -118,12 +117,12 @@ class BufferSelectionTool(FlatCAMTool):
         self.buffer_int_button.clicked.connect(self.on_buffer_int)
         self.buffer_ext_button.clicked.connect(self.on_buffer_ext)
 
-        # Init GUI
+        # Init AppGUI
         self.buffer_distance_entry.set_value(0.01)
 
     def run(self):
         self.app.defaults.report_usage("Geo Editor ToolBuffer()")
-        FlatCAMTool.run(self)
+        AppTool.run(self)
 
         # if the splitter us hidden, display it
         if self.app.ui.splitter.sizes()[0] == 0:
@@ -187,7 +186,7 @@ class BufferSelectionTool(FlatCAMTool):
         self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
 
 
-class TextInputTool(FlatCAMTool):
+class TextInputTool(AppTool):
     """
     Simple input for buffer distance.
     """
@@ -195,7 +194,7 @@ class TextInputTool(FlatCAMTool):
     toolName = "Text Input Tool"
 
     def __init__(self, app):
-        FlatCAMTool.__init__(self, app)
+        AppTool.__init__(self, app)
 
         self.app = app
         self.text_path = []
@@ -340,7 +339,7 @@ class TextInputTool(FlatCAMTool):
 
     def run(self):
         self.app.defaults.report_usage("Geo Editor TextInputTool()")
-        FlatCAMTool.run(self)
+        AppTool.run(self)
 
         # if the splitter us hidden, display it
         if self.app.ui.splitter.sizes()[0] == 0:
@@ -405,7 +404,7 @@ class TextInputTool(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Tool"))
 
 
-class PaintOptionsTool(FlatCAMTool):
+class PaintOptionsTool(AppTool):
     """
     Inputs to specify how to paint the selected polygons.
     """
@@ -413,7 +412,7 @@ class PaintOptionsTool(FlatCAMTool):
     toolName = "Paint Tool"
 
     def __init__(self, app, fcdraw):
-        FlatCAMTool.__init__(self, app)
+        AppTool.__init__(self, app)
 
         self.app = app
         self.fcdraw = fcdraw
@@ -538,7 +537,7 @@ class PaintOptionsTool(FlatCAMTool):
 
     def run(self):
         self.app.defaults.report_usage("Geo Editor ToolPaint()")
-        FlatCAMTool.run(self)
+        AppTool.run(self)
 
         # if the splitter us hidden, display it
         if self.app.ui.splitter.sizes()[0] == 0:
@@ -547,7 +546,7 @@ class PaintOptionsTool(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Paint Tool"))
 
     def set_tool_ui(self):
-        # Init GUI
+        # Init AppGUI
         if self.app.defaults["tools_painttooldia"]:
             self.painttooldia_entry.set_value(self.app.defaults["tools_painttooldia"])
         else:
@@ -599,7 +598,7 @@ class PaintOptionsTool(FlatCAMTool):
         self.app.ui.splitter.setSizes([0, 1])
 
 
-class TransformEditorTool(FlatCAMTool):
+class TransformEditorTool(AppTool):
     """
     Inputs to specify how to paint the selected polygons.
     """
@@ -612,7 +611,7 @@ class TransformEditorTool(FlatCAMTool):
     offsetName = _("Offset")
 
     def __init__(self, app, draw_app):
-        FlatCAMTool.__init__(self, app)
+        AppTool.__init__(self, app)
 
         self.app = app
         self.draw_app = draw_app
@@ -981,7 +980,7 @@ class TransformEditorTool(FlatCAMTool):
 
     def run(self):
         self.app.defaults.report_usage("Geo Editor Transform Tool()")
-        FlatCAMTool.run(self)
+        AppTool.run(self)
         self.set_tool_ui()
 
         # if the splitter us hidden, display it
@@ -991,7 +990,7 @@ class TransformEditorTool(FlatCAMTool):
         self.app.ui.notebook.setTabText(2, _("Transform Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
+        AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
 
     def set_tool_ui(self):
         # Initialize form
@@ -3383,7 +3382,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
             self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
             self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
         else:
-            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
             self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_geo_editor')
             self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_geo_editor')
 
@@ -3467,22 +3466,32 @@ class FlatCAMGeoEditor(QtCore.QObject):
             :return:
             """
             try:
-                self.options[opt] = float(entry.text())
+                text_value = entry.text()
+                if ',' in text_value:
+                    text_value = text_value.replace(',', '.')
+                self.options[opt] = float(text_value)
             except Exception as e:
+                entry.set_value(self.app.defaults[opt])
                 log.debug("FlatCAMGeoEditor.__init__().entry2option() --> %s" % str(e))
                 return
 
-        def gridx_changed(goption, gentry):
+        def grid_changed(goption, gentry):
             """
 
-            :param goption: String. Can be either 'global_gridx' or 'global_gridy'
-            :param gentry:  A GUI element which text value is read and used
+            :param goption:     String. Can be either 'global_gridx' or 'global_gridy'
+            :param gentry:      A GUI element which text value is read and used
             :return:
             """
+            if goption not in ['global_gridx', 'global_gridy']:
+                return
+
             entry2option(opt=goption, entry=gentry)
             # if the grid link is checked copy the value in the GridX field to GridY
             try:
-                val = float(gentry.get_value())
+                text_value = gentry.text()
+                if ',' in text_value:
+                    text_value = text_value.replace(',', '.')
+                val = float(text_value)
             except ValueError:
                 return
 
@@ -3491,7 +3500,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         self.app.ui.grid_gap_x_entry.setValidator(QtGui.QDoubleValidator())
         self.app.ui.grid_gap_x_entry.textChanged.connect(
-            lambda: gridx_changed("global_gridx", self.app.ui.grid_gap_x_entry))
+            lambda: grid_changed("global_gridx", self.app.ui.grid_gap_x_entry))
 
         self.app.ui.grid_gap_y_entry.setValidator(QtGui.QDoubleValidator())
         self.app.ui.grid_gap_y_entry.textChanged.connect(
@@ -3542,7 +3551,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
-        log.debug("Initialization of the FlatCAM Geometry Editor is finished ...")
+        log.debug("Initialization of the Geometry Editor is finished ...")
 
     def pool_recreated(self, pool):
         self.shapes.pool = pool
@@ -3559,14 +3568,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # Remove anything else in the GUI Selected Tab
         self.app.ui.selected_scroll_area.takeWidget()
-        # Put ourselves in the GUI Selected Tab
+        # Put ourselves in the AppGUI Selected Tab
         self.app.ui.selected_scroll_area.setWidget(self.geo_edit_widget)
         # Switch notebook to Selected page
         self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
 
     def build_ui(self):
         """
-        Build the GUI in the Selected Tab for this editor
+        Build the AppGUI in the Selected Tab for this editor
 
         :return:
         """
@@ -3644,10 +3653,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.tool_shape.enabled = True
         self.app.app_cursor.enabled = True
 
-        self.app.ui.snap_max_dist_entry.setEnabled(True)
-        self.app.ui.corner_snap_btn.setEnabled(True)
-        self.app.ui.snap_magnet.setVisible(True)
         self.app.ui.corner_snap_btn.setVisible(True)
+        self.app.ui.snap_magnet.setVisible(True)
 
         self.app.ui.geo_editor_menu.setDisabled(False)
         self.app.ui.geo_editor_menu.menuAction().setVisible(True)
@@ -3658,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)
@@ -3675,7 +3682,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         self.item_selected.connect(self.on_geo_elem_selected)
 
-        # ## GUI Events
+        # ## AppGUI Events
         self.tw.itemSelectionChanged.connect(self.on_tree_selection_change)
         # self.tw.keyPressed.connect(self.app.ui.keyPressEvent)
         # self.tw.customContextMenuRequested.connect(self.on_menu_request)
@@ -3703,27 +3710,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.app.ui.geo_edit_toolbar.setDisabled(True)
 
         settings = QSettings("Open Source", "FlatCAM")
-        if settings.contains("layout"):
-            layout = settings.value('layout', type=str)
-            if layout == 'standard':
-                # self.app.ui.geo_edit_toolbar.setVisible(False)
-
-                self.app.ui.snap_max_dist_entry.setEnabled(False)
-                self.app.ui.corner_snap_btn.setEnabled(False)
-                self.app.ui.snap_magnet.setVisible(False)
-                self.app.ui.corner_snap_btn.setVisible(False)
-            else:
-                # self.app.ui.geo_edit_toolbar.setVisible(True)
-
-                self.app.ui.snap_max_dist_entry.setEnabled(False)
-                self.app.ui.corner_snap_btn.setEnabled(False)
-        else:
-            # self.app.ui.geo_edit_toolbar.setVisible(False)
-
-            self.app.ui.snap_magnet.setVisible(False)
-            self.app.ui.corner_snap_btn.setVisible(False)
-            self.app.ui.snap_max_dist_entry.setEnabled(False)
-            self.app.ui.corner_snap_btn.setEnabled(False)
+        self.app.ui.corner_snap_btn.setVisible(False)
+        self.app.ui.snap_magnet.setVisible(False)
 
         # set the Editor Toolbar visibility to what was before entering in the Editor
         self.app.ui.geo_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
@@ -3757,7 +3745,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
             pass
 
         try:
-            # ## GUI Events
+            # ## AppGUI Events
             self.tw.itemSelectionChanged.disconnect(self.on_tree_selection_change)
             # self.tw.keyPressed.connect(self.app.ui.keyPressEvent)
             # self.tw.customContextMenuRequested.connect(self.on_menu_request)
@@ -4100,7 +4088,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # start with GRID toolbar activated
         if self.app.ui.grid_snap_btn.isChecked() is False:
             self.app.ui.grid_snap_btn.trigger()
-            self.app.ui.on_grid_snap_triggered(state=True)
 
     def on_buffer_tool(self):
         buff_tool = BufferSelectionTool(self.app, self)
@@ -4148,9 +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[str, bool].emit(_("Grid Snap enabled."), False)
             self.app.app_cursor.enabled = True
         else:
             self.app.app_cursor.enabled = False
+            self.app.inform[str, bool].emit(_("Grid Snap disabled."), False)
 
     def on_canvas_click(self, event):
         """
@@ -4173,8 +4162,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
             self.pos = (self.pos[0], self.pos[1])
 
         if event.button == 1:
-            self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                                   "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
+            # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+            #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
 
             modifiers = QtWidgets.QApplication.keyboardModifiers()
             # If the SHIFT key is pressed when LMB is clicked then the coordinates are copied to clipboard
@@ -4261,18 +4250,23 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.snap_y = y
         self.app.mouse = [x, y]
 
-        # update the position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                           "<b>Y</b>: %.4f" % (x, y))
-
         if self.pos is None:
             self.pos = (0, 0)
         self.app.dx = x - self.pos[0]
         self.app.dy = y - self.pos[1]
 
-        # update the reference position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+        # # update the position label in the infobar since the APP mouse event handlers are disconnected
+        self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f&nbsp;" % (x, y))
+        #
+        # # update the reference position label in the infobar since the APP mouse event handlers are disconnected
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.app.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, x, units, y, units)
 
         if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
             pass
@@ -4665,7 +4659,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
     def select_tool(self, toolname):
         """
-        Selects a drawing tool. Impacts the object and GUI.
+        Selects a drawing tool. Impacts the object and AppGUI.
 
         :param toolname: Name of the tool.
         :return: None
@@ -4750,8 +4744,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
         Transfers the geometry tool shape buffer to the selected geometry
         object. The geometry already in the object are removed.
 
-        :param fcgeometry: GeometryObject
-        :return: None
+        :param fcgeometry:  GeometryObject
+        :return:            None
         """
         if self.multigeo_tool:
             fcgeometry.tools[self.multigeo_tool]['solid_geometry'] = []
@@ -4776,6 +4770,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
                 new_geo = linemerge(new_geo)
             fcgeometry.solid_geometry.append(new_geo)
 
+        self.deactivate()
+
     def update_options(self, obj):
         if self.paint_tooldia:
             obj.options['cnctooldia'] = deepcopy(str(self.paint_tooldia))

+ 119 - 125
flatcamEditors/FlatCAMGrbEditor.py → AppEditors/FlatCAMGrbEditor.py

@@ -14,15 +14,13 @@ import shapely.affinity as affinity
 
 from vispy.geometry import Rect
 
-import threading
-import time
 from copy import copy, deepcopy
 import logging
 
 from camlib import distance, arc, three_point_circle
-from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
+from AppGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
     EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
-from FlatCAMTool import FlatCAMTool
+from AppTool import AppTool
 
 import numpy as np
 from numpy.linalg import norm as numpy_norm
@@ -32,7 +30,7 @@ import math
 # import pngcanvas
 import traceback
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -1086,15 +1084,6 @@ class FCRegion(FCShapeTool):
 
         self.draw_app.app.inform.emit('[success] %s' % _("Done."))
 
-    def clean_up(self):
-        self.draw_app.selected = []
-        self.draw_app.apertures_table.clearSelection()
-        self.draw_app.plot_all()
-        try:
-            self.draw_app.app.jump_signal.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
     def on_key(self, key):
         # Jump to coords
         if key == QtCore.Qt.Key_J or key == 'J':
@@ -1160,16 +1149,36 @@ class FCRegion(FCShapeTool):
 
             return msg
 
+    def clean_up(self):
+        self.draw_app.selected = []
+        self.draw_app.apertures_table.clearSelection()
+        self.draw_app.plot_all()
+        try:
+            self.draw_app.app.jump_signal.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
 
-class FCTrack(FCRegion):
+class FCTrack(FCShapeTool):
     """
     Resulting type: Polygon
     """
     def __init__(self, draw_app):
-        FCRegion.__init__(self, draw_app)
+        DrawTool.__init__(self, draw_app)
         self.name = 'track'
         self.draw_app = draw_app
 
+        self.steps_per_circle = self.draw_app.app.defaults["gerber_circle_steps"]
+
+        size_ap = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size'])
+        self.buf_val = (size_ap / 2) if size_ap > 0 else 0.0000001
+
+        self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value())
+        self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
+
+        self.temp_points = []
+
+        self. final_click = False
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
         except Exception as e:
@@ -1183,52 +1192,23 @@ class FCTrack(FCRegion):
 
         self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
 
-    def make(self):
-        new_geo_el = {}
-        if len(self.temp_points) == 1:
-            new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
-                                                                 resolution=int(self.steps_per_circle / 4))
-            new_geo_el['follow'] = Point(self.temp_points)
-        else:
-            new_geo_el['solid'] = (LineString(self.temp_points).buffer(
-                self.buf_val, resolution=int(self.steps_per_circle / 4))).buffer(0)
-            new_geo_el['follow'] = LineString(self.temp_points)
-
-        self.geometry = DrawToolShape(new_geo_el)
-
-        self.draw_app.in_action = False
-        self.complete = True
-
-        self.draw_app.app.jump_signal.disconnect()
-
-        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
-
-    def clean_up(self):
-        self.draw_app.selected = []
-        self.draw_app.apertures_table.clearSelection()
-        self.draw_app.plot_all()
-        try:
-            self.draw_app.app.jump_signal.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
     def click(self, point):
         self.draw_app.in_action = True
-        try:
-            if point != self.points[-1]:
-                self.points.append(point)
-        except IndexError:
+
+        if not self.points:
             self.points.append(point)
+        elif point != self.points[-1]:
+            self.points.append(point)
+        else:
+            return
 
         new_geo_el = {}
 
         if len(self.temp_points) == 1:
-            new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
-                                                                 resolution=int(self.steps_per_circle / 4))
+            new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
             new_geo_el['follow'] = Point(self.temp_points)
         else:
-            new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val,
-                                                                      resolution=int(self.steps_per_circle / 4))
+            new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
             new_geo_el['follow'] = LineString(self.temp_points)
 
         self.draw_app.add_gerber_shape(DrawToolShape(new_geo_el),
@@ -1241,23 +1221,25 @@ class FCTrack(FCRegion):
 
         return ""
 
+    def update_grid_info(self):
+        self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value())
+        self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
+
     def utility_geometry(self, data=None):
         self.update_grid_info()
         new_geo_el = {}
 
-        if len(self.points) == 0:
-            new_geo_el['solid'] = Point(data).buffer(self.buf_val,
-                                                     resolution=int(self.steps_per_circle / 4))
-
+        if not self.points:
+            new_geo_el['solid'] = Point(data).buffer(self.buf_val, int(self.steps_per_circle))
             return DrawToolUtilityShape(new_geo_el)
-        elif len(self.points) > 0:
-
-            self.temp_points = [self.points[-1]]
+        else:
             old_x = self.points[-1][0]
             old_y = self.points[-1][1]
             x = data[0]
             y = data[1]
 
+            self.temp_points = [self.points[-1]]
+
             mx = abs(round((x - old_x) / self.gridx_size))
             my = abs(round((y - old_y) / self.gridy_size))
 
@@ -1305,14 +1287,30 @@ class FCTrack(FCRegion):
 
             self.temp_points.append(data)
             if len(self.temp_points) == 1:
-                new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
-                                                                     resolution=int(self.steps_per_circle / 4))
+                new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
                 return DrawToolUtilityShape(new_geo_el)
 
-            new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val,
-                                                                      resolution=int(self.steps_per_circle / 4))
+            new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
             return DrawToolUtilityShape(new_geo_el)
 
+    def make(self):
+        new_geo_el = {}
+        if len(self.temp_points) == 1:
+            new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
+            new_geo_el['follow'] = Point(self.temp_points)
+        else:
+            new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
+            new_geo_el['solid'] = new_geo_el['solid'].buffer(0)     # try to clean the geometry
+            new_geo_el['follow'] = LineString(self.temp_points)
+
+        self.geometry = DrawToolShape(new_geo_el)
+
+        self.draw_app.in_action = False
+        self.complete = True
+
+        self.draw_app.app.jump_signal.disconnect()
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
+
     def on_key(self, key):
         if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
             if len(self.points) > 0:
@@ -1405,6 +1403,15 @@ class FCTrack(FCRegion):
 
             return msg
 
+    def clean_up(self):
+        self.draw_app.selected = []
+        self.draw_app.apertures_table.clearSelection()
+        self.draw_app.plot_all()
+        try:
+            self.draw_app.app.jump_signal.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
 
 class FCDisc(FCShapeTool):
     """
@@ -2955,7 +2962,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # this var will store the state of the toolbar before starting the editor
         self.toolbar_old_state = False
 
-        # Init GUI
+        # Init AppGUI
         self.apdim_lbl.hide()
         self.apdim_entry.hide()
         self.gerber_obj = None
@@ -2967,7 +2974,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             self.tool_shape = self.canvas.new_shape_collection(layers=1)
             self.ma_annotation = self.canvas.new_text_group()
         else:
-            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
             self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_grb_editor')
             self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_grb_editor')
             self.ma_annotation = ShapeCollectionLegacy(
@@ -3110,7 +3117,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.complete = True
 
         self.set_ui()
-        log.debug("Initialization of the FlatCAM Gerber Editor is finished ...")
+        log.debug("Initialization of the Gerber Editor is finished ...")
 
     def pool_recreated(self, pool):
         self.shapes.pool = pool
@@ -3139,7 +3146,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             tt_aperture = self.sorted_apcode[i]
             self.tid2apcode[i + 1] = tt_aperture
 
-        # Init GUI
+        # Init AppGUI
 
         self.buffer_distance_entry.set_value(self.app.defaults["gerber_editor_buff_f"])
         self.scale_factor_entry.set_value(self.app.defaults["gerber_editor_scale_f"])
@@ -3428,7 +3435,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             else:
                 # deleted_tool_dia = float(self.apertures_table.item(self.apertures_table.currentRow(), 1).text())
                 if len(self.apertures_table.selectionModel().selectedRows()) == 0:
-                    self.app.inform.emit('[WARNING_NOTCL]%s' % _(" Select an aperture in Aperture Table"))
+                    self.app.inform.emit('[WARNING_NOTCL] %s' % _(" Select an aperture in Aperture Table"))
                     return
 
                 deleted_apcode_list = []
@@ -3685,10 +3692,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.shapes.enabled = True
         self.tool_shape.enabled = True
 
-        self.app.ui.snap_max_dist_entry.setEnabled(True)
-        self.app.ui.corner_snap_btn.setEnabled(True)
-        self.app.ui.snap_magnet.setVisible(True)
         self.app.ui.corner_snap_btn.setVisible(True)
+        self.app.ui.snap_magnet.setVisible(True)
 
         self.app.ui.grb_editor_menu.setDisabled(False)
         self.app.ui.grb_editor_menu.menuAction().setVisible(True)
@@ -3698,12 +3703,11 @@ 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:
             self.app.ui.grid_snap_btn.trigger()
-            self.app.ui.on_grid_snap_triggered(state=True)
 
         # adjust the visibility of some of the canvas context menu
         self.app.ui.popmenu_edit.setVisible(False)
@@ -3736,29 +3740,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.grb_edit_toolbar.setDisabled(True)
 
         settings = QSettings("Open Source", "FlatCAM")
-        if settings.contains("layout"):
-            layout = settings.value('layout', type=str)
-            if layout == 'standard':
-                # self.app.ui.exc_edit_toolbar.setVisible(False)
-
-                self.app.ui.snap_max_dist_entry.setEnabled(False)
-                self.app.ui.corner_snap_btn.setEnabled(False)
-                self.app.ui.snap_magnet.setVisible(False)
-                self.app.ui.corner_snap_btn.setVisible(False)
-            else:
-                # self.app.ui.exc_edit_toolbar.setVisible(True)
-
-                self.app.ui.snap_max_dist_entry.setEnabled(False)
-                self.app.ui.corner_snap_btn.setEnabled(False)
-                self.app.ui.snap_magnet.setVisible(True)
-                self.app.ui.corner_snap_btn.setVisible(True)
-        else:
-            # self.app.ui.exc_edit_toolbar.setVisible(False)
-
-            self.app.ui.snap_max_dist_entry.setEnabled(False)
-            self.app.ui.corner_snap_btn.setEnabled(False)
-            self.app.ui.snap_magnet.setVisible(False)
-            self.app.ui.corner_snap_btn.setVisible(False)
+        self.app.ui.corner_snap_btn.setVisible(False)
+        self.app.ui.snap_magnet.setVisible(False)
 
         # set the Editor Toolbar visibility to what was before entering in the Editor
         self.app.ui.grb_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
@@ -4210,7 +4193,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
     def on_multiprocessing_finished(self):
         self.app.proc_container.update_view_text(' %s' % _("Setting up the UI"))
-        self.app.inform.emit('[success] %s.' % _("Adding geometry finished. Preparing the GUI"))
+        self.app.inform.emit('[success] %s.' % _("Adding geometry finished. Preparing the AppGUI"))
         self.set_ui()
         self.build_ui(first_run=True)
         self.plot_all()
@@ -4245,6 +4228,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             new_grb_name = self.edited_obj_name + "_edit"
 
         self.app.worker_task.emit({'fcn': self.new_edited_gerber, 'params': [new_grb_name, self.storage_dict]})
+        # self.new_edited_gerber(new_grb_name, self.storage_dict)
 
     @staticmethod
     def update_options(obj):
@@ -4266,9 +4250,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
         """
         Creates a new Gerber object for the edited Gerber. Thread-safe.
 
-        :param outname: Name of the resulting object. None causes the name to be that of the file.
-        :type outname: str
-        :param aperture_storage: a dictionary that holds all the objects geometry
+        :param outname:             Name of the resulting object. None causes the name to be that of the file.
+        :type outname:              str
+        :param aperture_storage:    a dictionary that holds all the objects geometry
+        :type aperture_storage:     dict
         :return: None
         """
 
@@ -4360,26 +4345,27 @@ class FlatCAMGrbEditor(QtCore.QObject):
                 self.app.inform.emit('[ERROR_NOTCL] %s' %
                                      _("There are no Aperture definitions in the file. Aborting Gerber creation."))
             except Exception:
-                msg = '[ERROR] %s' % \
-                      _("An internal error has occurred. See shell.\n")
+                msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
                 msg += traceback.format_exc()
                 app_obj.inform.emit(msg)
                 raise
+
             grb_obj.source_file = self.app.export_gerber(obj_name=out_name, filename=None,
                                                          local_use=grb_obj, use_thread=False)
 
         with self.app.proc_container.new(_("Creating Gerber.")):
             try:
-                self.app.new_object("gerber", outname, obj_init)
+                self.app.app_obj.new_object("gerber", outname, obj_init)
             except Exception as e:
                 log.error("Error on Edited object creation: %s" % str(e))
                 # make sure to clean the previous results
                 self.results = []
                 return
 
-            self.app.inform.emit('[success] %s' % _("Done. Gerber editing finished."))
             # make sure to clean the previous results
             self.results = []
+            self.deactivate_grb_editor()
+            self.app.inform.emit('[success] %s' % _("Done. Gerber editing finished."))
 
     def on_tool_select(self, tool):
         """
@@ -4537,8 +4523,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
             self.pos = (self.pos[0], self.pos[1])
 
         if event.button == 1:
-            self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                                   "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
+            # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+            #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
 
             # Selection with left mouse button
             if self.active_tool is not None:
@@ -4550,8 +4536,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         self.app.defaults["global_point_clipboard_format"] %
                         (self.decimals, self.pos[0], self.decimals, self.pos[1])
                     )
-                    self.app.inform.emit('[success] %s' %
-                                         _("Coordinates copied to clipboard."))
+                    self.app.inform.emit('[success] %s' % _("Coordinates copied to clipboard."))
                     return
 
                 # Dispatch event to active_tool
@@ -4562,6 +4547,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                     if self.current_storage is not None:
                         self.on_grb_shape_complete(self.current_storage)
                         self.build_ui()
+
                     # MS: always return to the Select Tool if modifier key is not pressed
                     # else return to the current tool
                     key_modifier = QtWidgets.QApplication.keyboardModifiers()
@@ -4569,6 +4555,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         modifier_to_use = Qt.ControlModifier
                     else:
                         modifier_to_use = Qt.ShiftModifier
+
                     # if modifier key is pressed then we add to the selected list the current shape but if it's already
                     # in the selected list, we removed it. Therefore first click selects, second deselects.
                     if key_modifier == modifier_to_use:
@@ -4629,12 +4616,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         # if right click on canvas and the active tool need to be finished (like Path or Polygon)
                         # right mouse click will finish the action
                         if isinstance(self.active_tool, FCShapeTool):
-                            self.active_tool.click(self.app.geo_editor.snap(self.x, self.y))
-                            self.active_tool.make()
+                            if isinstance(self.active_tool, FCTrack):
+                                self.active_tool.make()
+                            else:
+                                self.active_tool.click(self.app.geo_editor.snap(self.x, self.y))
+                                self.active_tool.make()
                             if self.active_tool.complete:
                                 self.on_grb_shape_complete()
-                                self.app.inform.emit('[success] %s' %
-                                                     _("Done."))
+                                self.app.inform.emit('[success] %s' % _("Done."))
 
                                 # MS: always return to the Select Tool if modifier key is not pressed
                                 # else return to the current tool but not for FCTrack
@@ -4774,18 +4763,23 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.app.mouse = [x, y]
 
-        # update the position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   " 
-                                           "<b>Y</b>: %.4f" % (x, y))
-
         if self.pos is None:
             self.pos = (0, 0)
         self.app.dx = x - self.pos[0]
         self.app.dy = y - self.pos[1]
 
-        # update the reference position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: " 
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+        # # update the position label in the infobar since the APP mouse event handlers are disconnected
+        self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f&nbsp;" % (x, y))
+        #
+        # # update the reference position label in the infobar since the APP mouse event handlers are disconnected
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.app.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, x, units, y, units)
 
         self.update_utility_geometry(data=(x, y))
 
@@ -5032,7 +5026,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
     def select_tool(self, toolname):
         """
-        Selects a drawing tool. Impacts the object and GUI.
+        Selects a drawing tool. Impacts the object and AppGUI.
 
         :param toolname: Name of the tool.
         :return: None
@@ -5298,7 +5292,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
 
 
-class TransformEditorTool(FlatCAMTool):
+class TransformEditorTool(AppTool):
     """
     Inputs to specify how to paint the selected polygons.
     """
@@ -5311,7 +5305,7 @@ class TransformEditorTool(FlatCAMTool):
     offsetName = _("Offset")
 
     def __init__(self, app, draw_app):
-        FlatCAMTool.__init__(self, app)
+        AppTool.__init__(self, app)
 
         self.app = app
         self.draw_app = draw_app
@@ -5697,13 +5691,13 @@ class TransformEditorTool(FlatCAMTool):
             except AttributeError:
                 pass
 
-        FlatCAMTool.run(self)
+        AppTool.run(self)
         self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, _("Transform Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
+        AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
 
     def set_tool_ui(self):
         # Initialize form

+ 4 - 4
flatcamEditors/FlatCAMTextEditor.py → AppEditors/FlatCAMTextEditor.py

@@ -5,7 +5,7 @@
 # MIT Licence                                              #
 # ##########################################################
 
-from flatcamGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber
+from AppGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber
 from PyQt5 import QtPrintSupport, QtWidgets, QtCore, QtGui
 
 from reportlab.platypus import SimpleDocTemplate, Paragraph
@@ -15,7 +15,7 @@ from reportlab.lib.units import inch, mm
 # from io import StringIO
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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."))

+ 0 - 0
flatcamEditors/__init__.py → AppEditors/__init__.py


+ 181 - 0
AppGUI/ColumnarFlowLayout.py

@@ -0,0 +1,181 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# File by:  David Robertson (c)                            #
+# Date:     5/2020                                         #
+# License:  MIT Licence                                    #
+# ##########################################################
+
+import sys
+
+from PyQt5.QtCore import QPoint, QRect, QSize, Qt
+from PyQt5.QtWidgets import QLayout, QSizePolicy
+import math
+
+
+class ColumnarFlowLayout(QLayout):
+    def __init__(self, parent=None, margin=0, spacing=-1):
+        super().__init__(parent)
+
+        if parent is not None:
+            self.setContentsMargins(margin, margin, margin, margin)
+
+        self.setSpacing(spacing)
+        self.itemList = []
+
+    def __del__(self):
+        del_item = self.takeAt(0)
+        while del_item:
+            del_item = self.takeAt(0)
+
+    def addItem(self, item):
+        self.itemList.append(item)
+
+    def count(self):
+        return len(self.itemList)
+
+    def itemAt(self, index):
+        if 0 <= index < len(self.itemList):
+            return self.itemList[index]
+        return None
+
+    def takeAt(self, index):
+        if 0 <= index < len(self.itemList):
+            return self.itemList.pop(index)
+        return None
+
+    def expandingDirections(self):
+        return Qt.Orientations(Qt.Orientation(0))
+
+    def hasHeightForWidth(self):
+        return True
+
+    def heightForWidth(self, width):
+        height = self.doLayout(QRect(0, 0, width, 0), True)
+        return height
+
+    def setGeometry(self, rect):
+        super().setGeometry(rect)
+        self.doLayout(rect, False)
+
+    def sizeHint(self):
+        return self.minimumSize()
+
+    def minimumSize(self):
+        size = QSize()
+
+        for item in self.itemList:
+            size = size.expandedTo(item.minimumSize())
+
+        margin, _, _, _ = self.getContentsMargins()
+
+        size += QSize(2 * margin, 2 * margin)
+        return size
+
+    def doLayout(self, rect: QRect, testOnly: bool) -> int:
+        spacing = self.spacing()
+        x = rect.x()
+        y = rect.y()
+
+        # Determine width of widest item
+        widest = 0
+        for item in self.itemList:
+            widest = max(widest, item.sizeHint().width())
+
+        # Determine how many equal-width columns we can get, and how wide each one should be
+        column_count = math.floor(rect.width() / (widest + spacing))
+        column_count = min(column_count, len(self.itemList))
+        column_count = max(1, column_count)
+        column_width = math.floor((rect.width() - (column_count-1)*spacing - 1) / column_count)
+
+        # Get the heights for all of our items
+        item_heights = {}
+        for item in self.itemList:
+            height = item.heightForWidth(column_width) if item.hasHeightForWidth() else item.sizeHint().height()
+            item_heights[item] = height
+
+        # Prepare our column representation
+        column_contents = []
+        column_heights = []
+        for column_index in range(column_count):
+            column_contents.append([])
+            column_heights.append(0)
+
+        def add_to_column(column: int, item):
+            column_contents[column].append(item)
+            column_heights[column] += (item_heights[item] + spacing)
+
+        def shove_one(from_column: int) -> bool:
+            if len(column_contents[from_column]) >= 1:
+                item = column_contents[from_column].pop(0)
+                column_heights[from_column] -= (item_heights[item] + spacing)
+                add_to_column(from_column-1, item)
+                return True
+            return False
+
+        def shove_cascade_consider(from_column: int) -> bool:
+            changed_item = False
+
+            if len(column_contents[from_column]) > 1:
+                item = column_contents[from_column][0]
+                item_height = item_heights[item]
+                if column_heights[from_column-1] + item_height < max(column_heights):
+                    changed_item = shove_one(from_column) or changed_item
+
+            if from_column+1 < column_count:
+                changed_item = shove_cascade_consider(from_column+1) or changed_item
+
+            return changed_item
+
+        def shove_cascade() -> bool:
+            if column_count < 2:
+                return False
+            changed_item = True
+            while changed_item:
+                changed_item = shove_cascade_consider(1)
+            return changed_item
+
+        def pick_best_shoving_position() -> int:
+            best_pos = 1
+            best_height = sys.maxsize
+            for column_idx in range(1, column_count):
+                if len(column_contents[column_idx]) == 0:
+                    continue
+                item = column_contents[column_idx][0]
+                height_after_shove = column_heights[column_idx-1] + item_heights[item]
+                if height_after_shove < best_height:
+                    best_height = height_after_shove
+                    best_pos = column_idx
+            return best_pos
+
+        # Calculate the best layout
+        column_index = 0
+        for item in self.itemList:
+            item_height = item_heights[item]
+            if column_heights[column_index] != 0 and (column_heights[column_index] + item_height) > max(column_heights):
+                column_index += 1
+                if column_index >= column_count:
+                    # Run out of room, need to shove more stuff in each column
+                    if column_count >= 2:
+                        changed = shove_cascade()
+                        if not changed:
+                            shoving_pos = pick_best_shoving_position()
+                            shove_one(shoving_pos)
+                            shove_cascade()
+                    column_index = column_count-1
+
+            add_to_column(column_index, item)
+
+        shove_cascade()
+
+        # Set geometry according to the layout we have calculated
+        if not testOnly:
+            for column_index, items in enumerate(column_contents):
+                x = column_index * (column_width + spacing)
+                y = 0
+                for item in items:
+                    height = item_heights[item]
+                    item.setGeometry(QRect(x, y, column_width, height))
+                    y += (height + spacing)
+
+        # Return the overall height
+        return max(column_heights)

+ 391 - 14
flatcamGUI/GUIElements.py → AppGUI/GUIElements.py

@@ -23,7 +23,7 @@ import html
 import sys
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 log = logging.getLogger('base')
@@ -570,9 +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):
@@ -599,7 +603,6 @@ class EvalEntry(QtWidgets.QLineEdit):
 
     def get_value(self):
         raw = str(self.text()).strip(' ')
-        evaled = 0.0
         try:
             evaled = eval(raw)
         except Exception as e:
@@ -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:
@@ -656,6 +659,132 @@ class EvalEntry2(QtWidgets.QLineEdit):
         return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
 
 
+class NumericalEvalEntry(EvalEntry):
+    """
+    Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
+    """
+    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(FCEntry):
+    """
+    Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
+    """
+    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 FCColorEntry(QtWidgets.QFrame):
+
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+
+        self.entry = FCEntry()
+        regex = QtCore.QRegExp("[#A-Fa-f0-9]*")
+        validator = QtGui.QRegExpValidator(regex, self.entry)
+        self.entry.setValidator(validator)
+
+        self.button = QtWidgets.QPushButton()
+        self.button.setFixedSize(15, 15)
+        self.button.setStyleSheet("border-color: dimgray;")
+
+        self.layout = QtWidgets.QHBoxLayout()
+        self.layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.layout.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.entry)
+        self.layout.addWidget(self.button)
+        self.setLayout(self.layout)
+
+        self.entry.editingFinished.connect(self._sync_button_color)
+        self.button.clicked.connect(self._on_button_clicked)
+
+        self.editingFinished = self.entry.editingFinished
+
+    def get_value(self) -> str:
+        return self.entry.get_value()
+
+    def set_value(self, value: str):
+        self.entry.set_value(value)
+        self._sync_button_color()
+
+    def _sync_button_color(self):
+        value = self.get_value()
+        self.button.setStyleSheet("background-color:%s;" % self._extract_color(value))
+
+    def _on_button_clicked(self):
+        value = self.entry.get_value()
+        current_color = QtGui.QColor(self._extract_color(value))
+
+        color_dialog = QtWidgets.QColorDialog()
+        selected_color = color_dialog.getColor(initial=current_color, options=QtWidgets.QColorDialog.ShowAlphaChannel)
+
+        if selected_color.isValid() is False:
+            return
+
+        new_value = str(selected_color.name()) + self._extract_alpha(value)
+        self.set_value(new_value)
+
+    def _extract_color(self, value: str) -> str:
+        return value[:7]
+
+    def _extract_alpha(self, value: str) -> str:
+        return value[7:9]
+
+
+class FCSliderWithSpinner(QtWidgets.QFrame):
+
+    def __init__(self, min=0, max=100, step=1, **kwargs):
+        super().__init__(**kwargs)
+
+        self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
+        self.slider.setMinimum(min)
+        self.slider.setMaximum(max)
+        self.slider.setSingleStep(step)
+
+        self.spinner = FCSpinner()
+        self.spinner.set_range(min, max)
+        self.spinner.set_step(step)
+        self.spinner.setMinimumWidth(70)
+
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+        self.spinner.setSizePolicy(sizePolicy)
+
+        self.layout = QtWidgets.QHBoxLayout()
+        self.layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.layout.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.slider)
+        self.layout.addWidget(self.spinner)
+        self.setLayout(self.layout)
+
+        self.slider.valueChanged.connect(self._on_slider)
+        self.spinner.valueChanged.connect(self._on_spinner)
+
+        self.valueChanged = self.spinner.valueChanged
+
+    def get_value(self) -> int:
+        return self.spinner.get_value()
+
+    def set_value(self, value: int):
+        self.spinner.set_value(value)
+
+    def _on_spinner(self):
+        spinner_value = self.spinner.value()
+        self.slider.setValue(spinner_value)
+
+    def _on_slider(self):
+        slider_value = self.slider.value()
+        self.spinner.set_value(slider_value)
+
+
 class FCSpinner(QtWidgets.QSpinBox):
 
     returnPressed = QtCore.pyqtSignal()
@@ -684,6 +813,8 @@ class FCSpinner(QtWidgets.QSpinBox):
             self.setAlignment(align_val)
 
         self.prev_readyToEdit = True
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred)
+        self.setSizePolicy(sizePolicy)
 
     def eventFilter(self, object, event):
         if event.type() == QtCore.QEvent.MouseButtonPress and self.prev_readyToEdit is True:
@@ -816,6 +947,8 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
             self.setAlignment(align_val)
 
         self.prev_readyToEdit = True
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred)
+        self.setSizePolicy(sizePolicy)
 
     def on_edit_finished(self):
         self.clearFocus()
@@ -2098,6 +2231,24 @@ class FCDetachableTab2(FCDetachableTab):
     def __init__(self, protect=None, protect_by_name=None, parent=None):
         super(FCDetachableTab2, self).__init__(protect=protect, protect_by_name=protect_by_name, parent=parent)
 
+        try:
+            self.tabBar.onCloseTabSignal.disconnect()
+        except TypeError:
+            pass
+
+        self.tabBar.onCloseTabSignal.connect(self.on_closetab_middle_button)
+
+    def on_closetab_middle_button(self, current_index):
+        """
+
+        :param current_index:
+        :return:
+        """
+
+        # if tab is protected don't delete it
+        if self.tabBar.tabButton(current_index, QtWidgets.QTabBar.RightSide) is not None:
+            self.closeTab(current_index)
+
     def closeTab(self, currentIndex):
         """
         Slot connected to the tabCloseRequested signal
@@ -2196,11 +2347,14 @@ class OptionalHideInputSection:
         """
         Associates the a checkbox with a set of inputs.
 
-        :param cb: Checkbox that enables the optional inputs.
-        :param optinputs: List of widgets that are optional.
-        :param logic: When True the logic is normal, when False the logic is in reverse
-        It means that for logic=True, when the checkbox is checked the widgets are Enabled, and
-        for logic=False, when the checkbox is checked the widgets are Disabled
+        :param cb:          Checkbox that enables the optional inputs.
+        :type cb:           QtWidgets.QCheckBox
+        :param optinputs:   List of widgets that are optional.
+        :type optinputs:    list
+        :param logic:       When True the logic is normal, when False the logic is in reverse
+                            It means that for logic=True, when the checkbox is checked the widgets are Enabled, and
+                            for logic=False, when the checkbox is checked the widgets are Disabled
+        :type logic:        bool
         :return:
         """
         assert isinstance(cb, FCCheckBox), \
@@ -2471,7 +2625,8 @@ class SpinBoxDelegate(QtWidgets.QItemDelegate):
     def updateEditorGeometry(self, editor, option, index):
         editor.setGeometry(option.rect)
 
-    def setDecimals(self, spinbox, digits):
+    @staticmethod
+    def setDecimals(spinbox, digits):
         spinbox.setDecimals(digits)
 
 
@@ -2519,7 +2674,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:
@@ -2762,9 +2917,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):
@@ -2920,9 +3078,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)
@@ -2938,6 +3096,225 @@ 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
+    """
+
+    def __init__(self, app, parent=None):
+        super().__init__(parent=parent)
+
+        self.app = app
+
+        if self.app.defaults["global_activity_icon"] == "Ball green":
+            icon = self.app.resource_location + '/active_2_static.png'
+            movie = self.app.resource_location + "/active_2.gif"
+        elif self.app.defaults["global_activity_icon"] == "Ball black":
+            icon = self.app.resource_location + '/active_static.png'
+            movie = self.app.resource_location + "/active.gif"
+        elif self.app.defaults["global_activity_icon"] == "Arrow green":
+            icon = self.app.resource_location + '/active_3_static.png'
+            movie = self.app.resource_location + "/active_3.gif"
+        elif self.app.defaults["global_activity_icon"] == "Eclipse green":
+            icon = self.app.resource_location + '/active_4_static.png'
+            movie = self.app.resource_location + "/active_4.gif"
+        else:
+            icon = self.app.resource_location + '/active_static.png'
+            movie = self.app.resource_location + "/active.gif"
+
+        self.setMinimumWidth(200)
+        self.movie_path = movie
+        self.icon_path = icon
+
+        self.icon = FCLabel(self)
+        self.icon.setGeometry(0, 0, 16, 12)
+        self.movie = QtGui.QMovie(self.movie_path)
+
+        self.icon.setMovie(self.movie)
+        # self.movie.start()
+
+        layout = QtWidgets.QHBoxLayout()
+        layout.setContentsMargins(5, 0, 5, 0)
+        layout.setAlignment(QtCore.Qt.AlignLeft)
+        self.setLayout(layout)
+
+        layout.addWidget(self.icon)
+        self.text = QtWidgets.QLabel(self)
+        self.text.setText(_("Idle."))
+        self.icon.setPixmap(QtGui.QPixmap(self.icon_path))
+
+        layout.addWidget(self.text)
+
+        self.icon.clicked.connect(self.app.on_toolbar_replot)
+
+    def set_idle(self):
+        self.movie.stop()
+        self.text.setText(_("Idle."))
+
+    def set_busy(self, msg, no_movie=None):
+        if no_movie is not True:
+            self.icon.setMovie(self.movie)
+            self.movie.start()
+        self.text.setText(msg)
+
+
+class FlatCAMInfoBar(QtWidgets.QWidget):
+    """
+    This class create a place to display the App messages in the Status Bar
+    """
+
+    def __init__(self, parent=None, app=None):
+        super(FlatCAMInfoBar, self).__init__(parent=parent)
+
+        self.app = app
+
+        self.icon = QtWidgets.QLabel(self)
+        self.icon.setGeometry(0, 0, 12, 12)
+        self.pmap = QtGui.QPixmap(self.app.resource_location + '/graylight12.png')
+        self.icon.setPixmap(self.pmap)
+
+        self.lock_pmaps = False
+
+        layout = QtWidgets.QHBoxLayout()
+        layout.setContentsMargins(5, 0, 5, 0)
+        self.setLayout(layout)
+
+        layout.addWidget(self.icon)
+
+        self.text = QtWidgets.QLabel(self)
+        self.text.setText(_("Application started ..."))
+        self.text.setToolTip(_("Hello!"))
+
+        layout.addWidget(self.text)
+        layout.addStretch()
+
+    def set_text_(self, text, color=None):
+        self.text.setText(text)
+        self.text.setToolTip(text)
+        if color:
+            self.text.setStyleSheet('color: %s' % str(color))
+
+    def set_status(self, text, level="info"):
+        level = str(level)
+
+        if self.lock_pmaps is not True:
+            self.pmap.fill()
+            if level == "ERROR" or level == "ERROR_NOTCL":
+                self.pmap = QtGui.QPixmap(self.app.resource_location + '/redlight12.png')
+            elif level.lower() == "success":
+                self.pmap = QtGui.QPixmap(self.app.resource_location + '/greenlight12.png')
+            elif level == "WARNING" or level == "WARNING_NOTCL":
+                self.pmap = QtGui.QPixmap(self.app.resource_location + '/yellowlight12.png')
+            elif level.lower() == "selected":
+                self.pmap = QtGui.QPixmap(self.app.resource_location + '/bluelight12.png')
+            else:
+                self.pmap = QtGui.QPixmap(self.app.resource_location + '/graylight12.png')
+
+        try:
+            self.set_text_(text)
+            self.icon.setPixmap(self.pmap)
+        except Exception as e:
+            log.debug("FlatCAMInfoBar.set_status() --> %s" % str(e))
+
+
+class FlatCAMSystemTray(QtWidgets.QSystemTrayIcon):
+    """
+    This class create the Sys Tray icon for the app
+    """
+
+    def __init__(self, app, icon, headless=None, parent=None):
+        # QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
+        super().__init__(icon, parent=parent)
+        self.app = app
+
+        menu = QtWidgets.QMenu(parent)
+
+        menu_runscript = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/script14.png'),
+                                           '%s' % _('Run Script ...'), self)
+        menu_runscript.setToolTip(
+            _("Will run the opened Tcl Script thus\n"
+              "enabling the automation of certain\n"
+              "functions of FlatCAM.")
+        )
+        menu.addAction(menu_runscript)
+
+        menu.addSeparator()
+
+        if headless is None:
+            self.menu_open = menu.addMenu(QtGui.QIcon(self.app.resource_location + '/folder32_bis.png'), _('Open'))
+
+            # Open Project ...
+            menu_openproject = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/folder16.png'),
+                                                 _('Open Project ...'), self)
+            self.menu_open.addAction(menu_openproject)
+            self.menu_open.addSeparator()
+
+            # Open Gerber ...
+            menu_opengerber = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/flatcam_icon24.png'),
+                                                _('Open &Gerber ...\tCtrl+G'), self)
+            self.menu_open.addAction(menu_opengerber)
+
+            # Open Excellon ...
+            menu_openexcellon = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'),
+                                                  _('Open &Excellon ...\tCtrl+E'), self)
+            self.menu_open.addAction(menu_openexcellon)
+
+            # Open G-Code ...
+            menu_opengcode = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/code.png'),
+                                               _('Open G-&Code ...'), self)
+            self.menu_open.addAction(menu_opengcode)
+
+            self.menu_open.addSeparator()
+
+            menu_openproject.triggered.connect(self.app.on_file_openproject)
+            menu_opengerber.triggered.connect(self.app.on_fileopengerber)
+            menu_openexcellon.triggered.connect(self.app.on_fileopenexcellon)
+            menu_opengcode.triggered.connect(self.app.on_fileopengcode)
+
+        exitAction = menu.addAction(_("Exit"))
+        exitAction.setIcon(QtGui.QIcon(self.app.resource_location + '/power16.png'))
+        self.setContextMenu(menu)
+
+        menu_runscript.triggered.connect(lambda: self.app.on_filerunscript(
+            silent=True if self.app.cmd_line_headless == 1 else False))
+
+        exitAction.triggered.connect(self.app.final_save)
+
+
+def message_dialog(title, message, kind="info", parent=None):
+    """
+    Builds and show a custom QMessageBox to be used in FlatCAM.
+
+    :param title:       title of the QMessageBox
+    :param message:     message to be displayed
+    :param kind:        type of QMessageBox; will display a specific icon.
+    :param parent:      parent
+    :return:            None
+    """
+    icon = {"info": QtWidgets.QMessageBox.Information,
+            "warning": QtWidgets.QMessageBox.Warning,
+            "error": QtWidgets.QMessageBox.Critical}[str(kind)]
+    dlg = QtWidgets.QMessageBox(icon, title, message, parent=parent)
+    dlg.setText(message)
+    dlg.exec_()
+
+
 def rreplace(s, old, new, occurrence):
     """
     Credits go here:

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 142 - 899
AppGUI/MainGUI.py


+ 151 - 351
flatcamGUI/ObjectUI.py → AppGUI/ObjectUI.py

@@ -11,11 +11,11 @@
 # Date: 3/10/2019                                          #
 # ##########################################################
 
-from flatcamGUI.GUIElements import *
+from AppGUI.GUIElements import *
 import sys
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -35,7 +35,7 @@ class ObjectUI(QtWidgets.QWidget):
     put UI elements in ObjectUI.custom_box (QtWidgets.QLayout).
     """
 
-    def __init__(self, app, icon_file='assets/resources/flatcam_icon32.png', title=_('FlatCAM Object'),
+    def __init__(self, app, icon_file='assets/resources/flatcam_icon32.png', title=_('App Object'),
                  parent=None, common=True):
         QtWidgets.QWidget.__init__(self, parent=parent)
 
@@ -114,7 +114,7 @@ class ObjectUI(QtWidgets.QWidget):
             self.common_grid.addWidget(self.transform_label, 2, 0, 1, 2)
 
             # ### Scale ####
-            self.scale_entry = FCEntry()
+            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 = EvalEntry2()
+            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"
@@ -149,21 +149,30 @@ class ObjectUI(QtWidgets.QWidget):
             self.common_grid.addWidget(self.offsetvector_entry, 4, 0)
             self.common_grid.addWidget(self.offset_button, 4, 1)
 
+            self.transformations_button = QtWidgets.QPushButton(_('Transformations'))
+            self.transformations_button.setToolTip(
+                _("Geometrical transformations of the current object.")
+            )
+            self.common_grid.addWidget(self.transformations_button, 5, 0, 1, 2)
+
         layout.addStretch()
     
     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 +214,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,254 +301,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."))
-        grid1.addWidget(self.combine_passes_cb, 8, 0)
-
-        # avoid an area from isolation
-        self.except_cb = FCCheckBox(label=_('Except'))
-        grid1.addWidget(self.follow_cb, 8, 1)
-
-        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.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"
@@ -537,19 +309,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.custom_box.addWidget(self.create_buffer_button)
 
-        self.ohis_iso = OptionalHideInputSection(
-            self.except_cb,
-            [self.type_obj_combo, self.type_obj_combo_label, self.obj_combo, self.obj_label],
-            logic=True
-        )
-
-        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 #######################
@@ -563,14 +328,21 @@ class GerberObjectUI(ObjectUI):
         self.tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("TOOLS"))
         grid2.addWidget(self.tool_lbl, 0, 0, 1, 2)
 
-        # ## Clear non-copper regions
-        self.clearcopper_label = QtWidgets.QLabel("%s" % _("Clear N-copper"))
-        self.clearcopper_label.setToolTip(
+        # 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"
-              "toolpaths to cut all non-copper regions.")
+              "toolpaths to cut around polygons.")
         )
-        self.clearcopper_label.setMinimumWidth(90)
+        self.iso_button.setStyleSheet("""
+                                      QPushButton
+                                      {
+                                          font-weight: bold;
+                                      }
+                                      """)
+        grid2.addWidget(self.iso_button, 1, 0, 1, 2)
 
+        # ## Clear non-copper regions
         self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool'))
         self.generate_ncc_button.setToolTip(
             _("Create the Geometry Object\n"
@@ -582,17 +354,9 @@ class GerberObjectUI(ObjectUI):
                             font-weight: bold;
                         }
                         """)
-        grid2.addWidget(self.clearcopper_label, 1, 0)
-        grid2.addWidget(self.generate_ncc_button, 1, 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"
@@ -604,13 +368,12 @@ class GerberObjectUI(ObjectUI):
                             font-weight: bold;
                         }
                         """)
-        grid2.addWidget(self.board_cutout_label, 2, 0)
-        grid2.addWidget(self.generate_cutout_button, 2, 1)
+        grid2.addWidget(self.generate_cutout_button, 3, 0, 1, 2)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid2.addWidget(separator_line, 3, 0, 1, 2)
+        grid2.addWidget(separator_line, 4, 0, 1, 2)
 
         # ## Non-copper regions
         self.noncopper_label = QtWidgets.QLabel("<b>%s</b>" % _("Non-copper regions"))
@@ -622,7 +385,7 @@ class GerberObjectUI(ObjectUI):
               "copper from a specified region.")
         )
 
-        grid2.addWidget(self.noncopper_label, 4, 0, 1, 2)
+        grid2.addWidget(self.noncopper_label, 5, 0, 1, 2)
 
         # Margin
         bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
@@ -638,8 +401,8 @@ class GerberObjectUI(ObjectUI):
         self.noncopper_margin_entry.set_precision(self.decimals)
         self.noncopper_margin_entry.setSingleStep(0.1)
 
-        grid2.addWidget(bmlabel, 5, 0)
-        grid2.addWidget(self.noncopper_margin_entry, 5, 1)
+        grid2.addWidget(bmlabel, 6, 0)
+        grid2.addWidget(self.noncopper_margin_entry, 6, 1)
 
         # Rounded corners
         self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
@@ -649,13 +412,13 @@ class GerberObjectUI(ObjectUI):
         self.noncopper_rounded_cb.setMinimumWidth(90)
 
         self.generate_noncopper_button = QtWidgets.QPushButton(_('Generate Geo'))
-        grid2.addWidget(self.noncopper_rounded_cb, 6, 0)
-        grid2.addWidget(self.generate_noncopper_button, 6, 1)
+        grid2.addWidget(self.noncopper_rounded_cb, 7, 0)
+        grid2.addWidget(self.generate_noncopper_button, 7, 1)
 
         separator_line1 = QtWidgets.QFrame()
         separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid2.addWidget(separator_line1, 7, 0, 1, 2)
+        grid2.addWidget(separator_line1, 8, 0, 1, 2)
 
         # ## Bounding box
         self.boundingbox_label = QtWidgets.QLabel('<b>%s</b>' % _('Bounding Box'))
@@ -664,7 +427,7 @@ class GerberObjectUI(ObjectUI):
               "Square shape.")
         )
 
-        grid2.addWidget(self.boundingbox_label, 8, 0, 1, 2)
+        grid2.addWidget(self.boundingbox_label, 9, 0, 1, 2)
 
         bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
         bbmargin.setToolTip(
@@ -677,8 +440,8 @@ class GerberObjectUI(ObjectUI):
         self.bbmargin_entry.set_precision(self.decimals)
         self.bbmargin_entry.setSingleStep(0.1)
 
-        grid2.addWidget(bbmargin, 9, 0)
-        grid2.addWidget(self.bbmargin_entry, 9, 1)
+        grid2.addWidget(bbmargin, 10, 0)
+        grid2.addWidget(self.bbmargin_entry, 10, 1)
 
         self.bbrounded_cb = FCCheckBox(label=_("Rounded Geo"))
         self.bbrounded_cb.setToolTip(
@@ -693,8 +456,8 @@ class GerberObjectUI(ObjectUI):
         self.generate_bb_button.setToolTip(
             _("Generate the Geometry object.")
         )
-        grid2.addWidget(self.bbrounded_cb, 10, 0)
-        grid2.addWidget(self.generate_bb_button, 10, 1)
+        grid2.addWidget(self.bbrounded_cb, 11, 0)
+        grid2.addWidget(self.generate_bb_button, 11, 1)
 
 
 class ExcellonObjectUI(ObjectUI):
@@ -723,22 +486,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)
@@ -1525,12 +1304,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()
@@ -1694,19 +1489,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)
@@ -1754,10 +1554,10 @@ class GeometryObjectUI(ObjectUI):
         # ################# GRID LAYOUT 3   ###############################
         # #################################################################
 
-        grid3 = QtWidgets.QGridLayout()
-        grid3.setColumnStretch(0, 0)
-        grid3.setColumnStretch(1, 1)
-        self.geo_param_box.addLayout(grid3)
+        self.grid3 = QtWidgets.QGridLayout()
+        self.grid3.setColumnStretch(0, 0)
+        self.grid3.setColumnStretch(1, 1)
+        self.geo_param_box.addLayout(self.grid3)
 
         # ### Tools Data ## ##
         self.tool_data_label = QtWidgets.QLabel(
@@ -1768,7 +1568,7 @@ class GeometryObjectUI(ObjectUI):
                 "Each tool store it's own set of such data."
             )
         )
-        grid3.addWidget(self.tool_data_label, 0, 0, 1, 2)
+        self.grid3.addWidget(self.tool_data_label, 0, 0, 1, 2)
 
         # Tip Dia
         self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
@@ -1782,8 +1582,8 @@ class GeometryObjectUI(ObjectUI):
         self.tipdia_entry.set_range(0.00001, 9999.9999)
         self.tipdia_entry.setSingleStep(0.1)
 
-        grid3.addWidget(self.tipdialabel, 1, 0)
-        grid3.addWidget(self.tipdia_entry, 1, 1)
+        self.grid3.addWidget(self.tipdialabel, 1, 0)
+        self.grid3.addWidget(self.tipdia_entry, 1, 1)
 
         # Tip Angle
         self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
@@ -1798,8 +1598,8 @@ class GeometryObjectUI(ObjectUI):
         self.tipangle_entry.set_range(1.0, 180.0)
         self.tipangle_entry.setSingleStep(1)
 
-        grid3.addWidget(self.tipanglelabel, 2, 0)
-        grid3.addWidget(self.tipangle_entry, 2, 1)
+        self.grid3.addWidget(self.tipanglelabel, 2, 0)
+        self.grid3.addWidget(self.tipangle_entry, 2, 1)
 
         # Cut Z
         self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
@@ -1819,8 +1619,8 @@ class GeometryObjectUI(ObjectUI):
 
         self.cutz_entry.setSingleStep(0.1)
 
-        grid3.addWidget(self.cutzlabel, 3, 0)
-        grid3.addWidget(self.cutz_entry, 3, 1)
+        self.grid3.addWidget(self.cutzlabel, 3, 0)
+        self.grid3.addWidget(self.cutz_entry, 3, 1)
 
         # Multi-pass
         self.mpass_cb = FCCheckBox('%s:' % _("Multi-Depth"))
@@ -1845,8 +1645,8 @@ class GeometryObjectUI(ObjectUI):
         )
         self.ois_mpass_geo = OptionalInputSection(self.mpass_cb, [self.maxdepth_entry])
 
-        grid3.addWidget(self.mpass_cb, 4, 0)
-        grid3.addWidget(self.maxdepth_entry, 4, 1)
+        self.grid3.addWidget(self.mpass_cb, 4, 0)
+        self.grid3.addWidget(self.maxdepth_entry, 4, 1)
 
         # Travel Z
         self.travelzlabel = QtWidgets.QLabel('%s:' % _('Travel Z'))
@@ -1864,8 +1664,8 @@ class GeometryObjectUI(ObjectUI):
 
         self.travelz_entry.setSingleStep(0.1)
 
-        grid3.addWidget(self.travelzlabel, 5, 0)
-        grid3.addWidget(self.travelz_entry, 5, 1)
+        self.grid3.addWidget(self.travelzlabel, 5, 0)
+        self.grid3.addWidget(self.travelz_entry, 5, 1)
 
         # Feedrate X-Y
         self.frlabel = QtWidgets.QLabel('%s:' % _('Feedrate X-Y'))
@@ -1878,8 +1678,8 @@ class GeometryObjectUI(ObjectUI):
         self.cncfeedrate_entry.set_range(0, 99999.9999)
         self.cncfeedrate_entry.setSingleStep(0.1)
 
-        grid3.addWidget(self.frlabel, 10, 0)
-        grid3.addWidget(self.cncfeedrate_entry, 10, 1)
+        self.grid3.addWidget(self.frlabel, 10, 0)
+        self.grid3.addWidget(self.cncfeedrate_entry, 10, 1)
 
         # Feedrate Z (Plunge)
         self.frzlabel = QtWidgets.QLabel('%s:' % _('Feedrate Z'))
@@ -1893,8 +1693,8 @@ class GeometryObjectUI(ObjectUI):
         self.feedrate_z_entry.set_range(0, 99999.9999)
         self.feedrate_z_entry.setSingleStep(0.1)
 
-        grid3.addWidget(self.frzlabel, 11, 0)
-        grid3.addWidget(self.feedrate_z_entry, 11, 1)
+        self.grid3.addWidget(self.frzlabel, 11, 0)
+        self.grid3.addWidget(self.feedrate_z_entry, 11, 1)
 
         # Feedrate rapids
         self.fr_rapidlabel = QtWidgets.QLabel('%s:' % _('Feedrate Rapids'))
@@ -1910,8 +1710,8 @@ class GeometryObjectUI(ObjectUI):
         self.feedrate_rapid_entry.set_range(0, 99999.9999)
         self.feedrate_rapid_entry.setSingleStep(0.1)
 
-        grid3.addWidget(self.fr_rapidlabel, 12, 0)
-        grid3.addWidget(self.feedrate_rapid_entry, 12, 1)
+        self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
+        self.grid3.addWidget(self.feedrate_rapid_entry, 12, 1)
         # default values is to hide
         self.fr_rapidlabel.hide()
         self.feedrate_rapid_entry.hide()
@@ -1936,8 +1736,8 @@ class GeometryObjectUI(ObjectUI):
               "meet with last cut, we generate an\n"
               "extended cut over the first cut section.")
         )
-        grid3.addWidget(self.extracut_cb, 13, 0)
-        grid3.addWidget(self.e_cut_entry, 13, 1)
+        self.grid3.addWidget(self.extracut_cb, 13, 0)
+        self.grid3.addWidget(self.e_cut_entry, 13, 1)
 
         # Spindlespeed
         self.spindle_label = QtWidgets.QLabel('%s:' % _('Spindle speed'))
@@ -1952,8 +1752,8 @@ class GeometryObjectUI(ObjectUI):
         self.cncspindlespeed_entry.set_range(0, 1000000)
         self.cncspindlespeed_entry.set_step(100)
 
-        grid3.addWidget(self.spindle_label, 14, 0)
-        grid3.addWidget(self.cncspindlespeed_entry, 14, 1)
+        self.grid3.addWidget(self.spindle_label, 14, 0)
+        self.grid3.addWidget(self.cncspindlespeed_entry, 14, 1)
 
         # Dwell
         self.dwell_cb = FCCheckBox('%s:' % _('Dwell'))
@@ -1973,8 +1773,8 @@ class GeometryObjectUI(ObjectUI):
         )
         self.ois_dwell_geo = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
 
-        grid3.addWidget(self.dwell_cb, 15, 0)
-        grid3.addWidget(self.dwelltime_entry, 15, 1)
+        self.grid3.addWidget(self.dwell_cb, 15, 0)
+        self.grid3.addWidget(self.dwelltime_entry, 15, 1)
 
         # Probe depth
         self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
@@ -1987,8 +1787,8 @@ class GeometryObjectUI(ObjectUI):
         self.pdepth_entry.set_range(-9999.9999, 9999.9999)
         self.pdepth_entry.setSingleStep(0.1)
 
-        grid3.addWidget(self.pdepth_label, 17, 0)
-        grid3.addWidget(self.pdepth_entry, 17, 1)
+        self.grid3.addWidget(self.pdepth_label, 17, 0)
+        self.grid3.addWidget(self.pdepth_entry, 17, 1)
 
         self.pdepth_label.hide()
         self.pdepth_entry.setVisible(False)
@@ -2003,8 +1803,8 @@ class GeometryObjectUI(ObjectUI):
         self.feedrate_probe_entry.set_range(0.0, 9999.9999)
         self.feedrate_probe_entry.setSingleStep(0.1)
 
-        grid3.addWidget(self.feedrate_probe_label, 18, 0)
-        grid3.addWidget(self.feedrate_probe_entry, 18, 1)
+        self.grid3.addWidget(self.feedrate_probe_label, 18, 0)
+        self.grid3.addWidget(self.feedrate_probe_entry, 18, 1)
 
         self.feedrate_probe_label.hide()
         self.feedrate_probe_entry.setVisible(False)

+ 152 - 14
flatcamGUI/PlotCanvas.py → AppGUI/PlotCanvas.py

@@ -8,13 +8,21 @@
 from PyQt5 import QtCore
 
 import logging
-from flatcamGUI.VisPyCanvas import VisPyCanvas, Color
-from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
-from vispy.scene.visuals import InfiniteLine, Line
+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')
 
 
@@ -54,8 +62,12 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
 
         if theme == 'white':
             self.line_color = (0.3, 0.0, 0.0, 1.0)
+            self.rect_hud_color = Color('#0000FF10')
+            self.text_hud_color = 'black'
         else:
             self.line_color = (0.4, 0.4, 0.4, 1.0)
+            self.rect_hud_color = Color('#80808040')
+            self.text_hud_color = 'white'
 
         # workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
         # which might decrease performance
@@ -129,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
@@ -146,13 +153,61 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
         self.cursor_h_line = InfiniteLine(pos=None, color=c_color, vertical=False,
                                           parent=self.line_parent)
 
+        # font size
+        qsettings = QtCore.QSettings("Open Source", "FlatCAM")
+        if qsettings.contains("hud_font_size"):
+            fsize = qsettings.value('hud_font_size', type=int)
+        else:
+            fsize = 8
+
+        # units
+        units = self.fcapp.defaults["units"].lower()
+
+        # coordinates and anchors
+        height = fsize * 11     # 90. Constant 11 is something that works
+        width = height * 2      # width is double the height = it is something that works
+        center_x = (width / 2) + 5
+        center_y = (height / 2) + 5
+
+        # text
+        self.text_hud = Text('', color=self.text_hud_color, pos=(10, center_y), method='gpu', anchor_x='left',
+                             parent=None)
+        self.text_hud.font_size = fsize
+        self.text_hud.text = 'Dx:\t%s [%s]\nDy:\t%s [%s]\n\nX:  \t%s [%s]\nY:  \t%s [%s]' % \
+                             ('0.0000', units, '0.0000', units, '0.0000', units, '0.0000', units)
+
+        # rectangle
+        self.rect_hud = Rectangle(center=(center_x, center_y), width=width, height=height, radius=[5, 5, 5, 5],
+                                  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()
         self.fcapp.pool_recreated.connect(self.on_pool_recreated)
         self.text_collection = self.new_text_collection()
 
-        # TODO: Should be setting to show/hide CNC job annotations (global or per object)
         self.text_collection.enabled = True
 
         self.c = None
@@ -163,6 +218,76 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
 
         self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
 
+    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: orange;
+                                                          }
+                                                          """)
+            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: mediumpurple;
+                                                  }
+                                                  """)
+            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):
         """
         Draw a rectangular shape on canvas to specify our valid workspace.
@@ -183,17 +308,30 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
 
         a = np.array([(0, 0), (dims[0], 0), (dims[0], dims[1]), (0, dims[1])])
 
-        if not self.workspace_line:
-            self.workspace_line = Line(pos=np.array((a[0], a[1], a[2], a[3], a[0])), color=(0.70, 0.3, 0.3, 0.7),
-                                       antialias=True, method='agg', parent=self.view.scene)
-        else:
-            self.workspace_line.parent = self.view.scene
+        # if not self.workspace_line:
+        #     self.workspace_line = Line(pos=np.array((a[0], a[1], a[2], a[3], a[0])), color=(0.70, 0.3, 0.3, 0.7),
+        #                                antialias=True, method='agg', parent=self.view.scene)
+        # else:
+        #     self.workspace_line.parent = self.view.scene
+        self.workspace_line = Line(pos=np.array((a[0], a[1], a[2], a[3], a[0])), color=(0.70, 0.3, 0.3, 0.7),
+                                   antialias=True, method='agg', parent=self.view.scene)
+
+        self.fcapp.ui.wplace_label.set_value(workspace_size[:3])
+        self.fcapp.ui.wplace_label.setToolTip(workspace_size)
+        self.fcapp.ui.wplace_label.setStyleSheet("""
+                        QLabel
+                        {
+                            color: black;
+                            background-color: olivedrab;
+                        }
+                        """)
 
     def delete_workspace(self):
         try:
             self.workspace_line.parent = None
         except Exception:
             pass
+        self.fcapp.ui.wplace_label.setStyleSheet("")
 
     # redraw the workspace lines on the plot by re adding them to the parent view.scene
     def restore_workspace(self):

+ 233 - 9
flatcamGUI/PlotCanvasLegacy.py → AppGUI/PlotCanvasLegacy.py

@@ -19,8 +19,10 @@ from shapely.geometry import Polygon, LineString, LinearRing
 from copy import deepcopy
 import logging
 
+import numpy as np
+
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 # Prevent conflict with Qt5 and above.
@@ -29,13 +31,13 @@ mpl_use("Qt5Agg")
 from matplotlib.figure import Figure
 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
 from matplotlib.lines import Line2D
+from matplotlib.offsetbox import AnchoredText
 # from matplotlib.widgets import Cursor
 
 fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
-
 log = logging.getLogger('base')
 
 
@@ -45,9 +47,9 @@ class CanvasCache(QtCore.QObject):
     Case story #1:
 
     1) No objects in the project.
-    2) Object is created (new_object() emits object_created(obj)).
+    2) Object is created (app_obj.new_object() emits object_created(obj)).
        on_object_created() adds (i) object to collection and emits
-       (ii) new_object_available() then calls (iii) object.plot()
+       (ii) app_obj.new_object_available() then calls (iii) object.plot()
     3) object.plot() creates axes if necessary on
        app.collection.figure. Then plots on it.
     4) Plots on a cache-size canvas (in background).
@@ -113,7 +115,7 @@ class CanvasCache(QtCore.QObject):
 
         # Continue to update the cache.
 
-    # def on_new_object_available(self):
+    # def on_app_obj.new_object_available(self):
     #
     #     log.debug("A new object is available. Should plot it!")
 
@@ -147,9 +149,13 @@ class PlotCanvasLegacy(QtCore.QObject):
         if self.app.defaults['global_theme'] == 'white':
             theme_color = '#FFFFFF'
             tick_color = '#000000'
+            self.rect_hud_color = '#0000FF10'
+            self.text_hud_color = '#000000'
         else:
             theme_color = '#000000'
             tick_color = '#FFFFFF'
+            self.rect_hud_color = '#80808040'
+            self.text_hud_color = '#FFFFFF'
 
         # workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
         # which might decrease performance
@@ -295,14 +301,163 @@ class PlotCanvasLegacy(QtCore.QObject):
         # signal is the mouse is dragging
         self.is_dragging = False
 
+        self.mouse_press_pos = None
+
         # signal if there is a doubleclick
         self.is_dblclk = False
 
+        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:
             self.draw_workspace(workspace_size=self.app.defaults["global_workspaceT"])
 
+        if self.app.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)
+
+    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: orange;
+                                                            }
+                                                            """)
+                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.app.ui.hud_label.setStyleSheet("""
+                                                QLabel
+                                                {
+                                                    color: black;
+                                                    background-color: mediumpurple;
+                                                }
+                                                """)
+            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.app.ui.hud_label.setStyleSheet("")
+            self.app.inform[str, bool].emit(_("HUD disabled."), False)
+
+        self.canvas.draw()
+
+    class Thud(QtCore.QObject):
+        text_changed = QtCore.pyqtSignal(str)
+
+        def __init__(self, plotcanvas):
+            super().__init__()
+
+            self.p = plotcanvas
+            units = self.p.app.defaults['units']
+            self._text = 'Dx:    %s [%s]\nDy:    %s [%s]\n\nX:      %s [%s]\nY:      %s [%s]' % \
+                         ('0.0000', units, '0.0000', units, '0.0000', units, '0.0000', units)
+
+            # set font size
+            qsettings = QtCore.QSettings("Open Source", "FlatCAM")
+            if qsettings.contains("hud_font_size"):
+                # I multiply with 2.5 because this seems to be the difference between the value taken by the VisPy (3D)
+                # and Matplotlib (Legacy2D FlatCAM graphic engine)
+                fsize = int(qsettings.value('hud_font_size', type=int) * 2.5)
+            else:
+                fsize = 20
+
+            self.hud_holder = AnchoredText(self._text, prop=dict(size=fsize), frameon=True, loc='upper left')
+            self.hud_holder.patch.set_boxstyle("round,pad=0.,rounding_size=0.2")
+
+            fc_color = self.p.rect_hud_color[:-2]
+            fc_alpha = int(self.p.rect_hud_color[-2:], 16) / 255
+            text_color = self.p.text_hud_color
+
+            self.hud_holder.patch.set_facecolor(fc_color)
+            self.hud_holder.patch.set_alpha(fc_alpha)
+            self.hud_holder.patch.set_edgecolor((0, 0, 0, 0))
+
+            self. hud_holder.txt._text.set_color(color=text_color)
+            self.text_changed.connect(self.on_text_changed)
+
+        @property
+        def text(self):
+            return self._text
+
+        @text.setter
+        def text(self, val):
+            self.text_changed.emit(val)
+            self._text = val
+
+        def on_text_changed(self, txt):
+            try:
+                txt = txt.replace('\t', '    ')
+                self.hud_holder.txt.set_text(txt)
+                self.p.canvas.draw()
+            except Exception:
+                pass
+
+        def add_artist(self):
+            if self.hud_holder not in self.p.axes.artists:
+                self.p.axes.add_artist(self.hud_holder)
+
+        def remove_artist(self):
+            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.
@@ -329,12 +484,23 @@ class PlotCanvasLegacy(QtCore.QObject):
             self.axes.add_line(self.workspace_line)
             self.canvas.draw()
 
+        self.app.ui.wplace_label.set_value(workspace_size[:3])
+        self.app.ui.wplace_label.setToolTip(workspace_size)
+        self.fcapp.ui.wplace_label.setStyleSheet("""
+                        QLabel
+                        {
+                            color: black;
+                            background-color: olivedrab;
+                        }
+                        """)
+
     def delete_workspace(self):
         try:
             self.axes.lines.remove(self.workspace_line)
             self.canvas.draw()
         except Exception:
             pass
+        self.fcapp.ui.wplace_label.setStyleSheet("")
 
     def graph_event_connect(self, event_name, callback):
         """
@@ -423,7 +589,7 @@ class PlotCanvasLegacy(QtCore.QObject):
 
             if self.big_cursor is False:
                 try:
-                    x, y = self.app.geo_editor.snap(x_pos, y_pos)
+                    x, y = self.snap(x_pos, y_pos)
 
                     # Pointer (snapped)
                     # The size of the cursor is multiplied by 1.65 because that value made the cursor similar with the
@@ -456,7 +622,7 @@ class PlotCanvasLegacy(QtCore.QObject):
                     pass
                 self.canvas.draw_idle()
 
-        self.canvas.blit(self.axes.bbox)
+            self.canvas.blit(self.axes.bbox)
 
     def clear_cursor(self, state):
         if state is True:
@@ -781,6 +947,7 @@ class PlotCanvasLegacy(QtCore.QObject):
     def on_mouse_press(self, event):
 
         self.is_dragging = True
+        self.mouse_press_pos = (event.x, event.y)
 
         # Check for middle mouse button press
         if self.app.defaults["global_pan_button"] == '2':
@@ -806,7 +973,11 @@ class PlotCanvasLegacy(QtCore.QObject):
 
     def on_mouse_release(self, event):
 
-        self.is_dragging = False
+        mouse_release_pos = (event.x, event.y)
+        delta = 0.05
+
+        if abs(self.distance(self.mouse_press_pos, mouse_release_pos)) < delta:
+            self.is_dragging = False
 
         # Check for middle mouse button release to complete pan procedure
         # Check for middle mouse button press
@@ -858,7 +1029,7 @@ class PlotCanvasLegacy(QtCore.QObject):
             self.canvas.draw_idle()
 
             # #### Temporary place-holder for cached update #####
-            self.update_screen_request.emit([0, 0, 0, 0, 0])
+            # self.update_screen_request.emit([0, 0, 0, 0, 0])
 
         if self.app.defaults["global_cursor_color_enabled"] is True:
             self.draw_cursor(x_pos=x, y_pos=y, color=self.app.cursor_color_3D)
@@ -910,6 +1081,59 @@ class PlotCanvasLegacy(QtCore.QObject):
 
         return width / xpx, height / ypx
 
+    def snap(self, x, y):
+        """
+        Adjusts coordinates to snap settings.
+
+        :param x: Input coordinate X
+        :param y: Input coordinate Y
+        :return: Snapped (x, y)
+        """
+
+        snap_x, snap_y = (x, y)
+        snap_distance = np.Inf
+
+        # ### Grid snap
+        if self.app.grid_status():
+            if self.app.defaults["global_gridx"] != 0:
+                try:
+                    snap_x_ = round(x / float(self.app.defaults["global_gridx"])) * \
+                              float(self.app.defaults["global_gridx"])
+                except TypeError:
+                    snap_x_ = x
+            else:
+                snap_x_ = x
+
+            # If the Grid_gap_linked on Grid Toolbar is checked then the snap distance on GridY entry will be ignored
+            # and it will use the snap distance from GridX entry
+            if self.app.ui.grid_gap_link_cb.isChecked():
+                if self.app.defaults["global_gridx"] != 0:
+                    try:
+                        snap_y_ = round(y / float(self.app.defaults["global_gridx"])) * \
+                                  float(self.app.defaults["global_gridx"])
+                    except TypeError:
+                        snap_y_ = y
+                else:
+                    snap_y_ = y
+            else:
+                if self.app.defaults["global_gridy"] != 0:
+                    try:
+                        snap_y_ = round(y / float(self.app.defaults["global_gridy"])) * \
+                                  float(self.app.defaults["global_gridy"])
+                    except TypeError:
+                        snap_y_ = y
+                else:
+                    snap_y_ = y
+            nearest_grid_distance = self.distance((x, y), (snap_x_, snap_y_))
+            if nearest_grid_distance < snap_distance:
+                snap_x, snap_y = (snap_x_, snap_y_)
+
+        return snap_x, snap_y
+
+    @staticmethod
+    def distance(pt1, pt2):
+        return np.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
+
 
 class FakeCursor(QtCore.QObject):
     """

+ 1 - 0
flatcamGUI/VisPyCanvas.py → AppGUI/VisPyCanvas.py

@@ -13,6 +13,7 @@ import numpy as np
 
 import vispy.scene as scene
 from vispy.scene.cameras.base_camera import BaseCamera
+# from vispy.scene.widgets import Widget as VisPyWidget
 from vispy.color import Color
 
 import time

+ 0 - 0
flatcamGUI/VisPyData/data/fonts/opensans-regular.ttf → AppGUI/VisPyData/data/fonts/opensans-regular.ttf


+ 0 - 0
flatcamGUI/VisPyData/data/freetype/freetype253.dll → AppGUI/VisPyData/data/freetype/freetype253.dll


+ 0 - 0
flatcamGUI/VisPyData/data/freetype/freetype253_x64.dll → AppGUI/VisPyData/data/freetype/freetype253_x64.dll


+ 0 - 0
flatcamGUI/VisPyPatches.py → AppGUI/VisPyPatches.py


+ 0 - 0
flatcamGUI/VisPyTesselators.py → AppGUI/VisPyTesselators.py


+ 1 - 1
flatcamGUI/VisPyVisuals.py → AppGUI/VisPyVisuals.py

@@ -13,7 +13,7 @@ from vispy.color import Color
 from shapely.geometry import Polygon, LineString, LinearRing
 import threading
 import numpy as np
-from flatcamGUI.VisPyTesselators import GLUTess
+from AppGUI.VisPyTesselators import GLUTess
 
 
 class FlatCAMLineVisual(LineVisual):

+ 0 - 0
flatcamGUI/__init__.py → AppGUI/__init__.py


+ 327 - 0
AppGUI/preferences/OptionUI.py

@@ -0,0 +1,327 @@
+from typing import Union, Sequence, List
+
+from PyQt5 import QtWidgets, QtGui
+from PyQt5.QtCore import QSettings
+
+from AppGUI.GUIElements import RadioSet, FCCheckBox, FCButton, FCComboBox, FCEntry, FCSpinner, FCColorEntry, \
+    FCSliderWithSpinner, FCDoubleSpinner, FloatEntry, FCTextArea
+
+import gettext
+import AppTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class OptionUI:
+
+    def __init__(self, option: str):
+        self.option = option
+
+    def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
+        """
+        Adds the necessary widget to the grid, starting at the supplied row.
+        Returns the number of rows used (normally 1)
+        """
+        raise NotImplementedError()
+
+    def get_field(self):
+        raise NotImplementedError()
+
+
+class BasicOptionUI(OptionUI):
+    """Abstract OptionUI that has a label on the left then some other widget on the right"""
+    def __init__(self, option: str, label_text: str, label_tooltip: Union[str, None] = None,
+                 label_bold: bool = False, label_color: Union[str, None] = None):
+        super().__init__(option=option)
+        self.label_text = label_text
+        self.label_tooltip = label_tooltip
+        self.label_bold = label_bold
+        self.label_color = label_color
+        self.label_widget = self.build_label_widget()
+        self.entry_widget = self.build_entry_widget()
+
+    def build_label_widget(self) -> QtWidgets.QLabel:
+        fmt = "%s:"
+        if self.label_bold:
+            fmt = "<b>%s</b>" % fmt
+        if self.label_color:
+            fmt = "<span style=\"color:%s;\">%s</span>" % (self.label_color, fmt)
+        label_widget = QtWidgets.QLabel(fmt % _(self.label_text))
+        if self.label_tooltip is not None:
+            label_widget.setToolTip(_(self.label_tooltip))
+        return label_widget
+
+    def build_entry_widget(self) -> QtWidgets.QWidget:
+        raise NotImplementedError()
+
+    def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
+        grid.addWidget(self.label_widget, row, 0)
+        grid.addWidget(self.entry_widget, row, 1)
+        return 1
+
+    def get_field(self):
+        return self.entry_widget
+
+
+class LineEntryOptionUI(BasicOptionUI):
+    def build_entry_widget(self) -> QtWidgets.QWidget:
+        return FCEntry()
+
+
+# Not sure why this is needed over DoubleSpinnerOptionUI
+class FloatEntryOptionUI(BasicOptionUI):
+    def build_entry_widget(self) -> QtWidgets.QWidget:
+        return FloatEntry()
+
+
+class RadioSetOptionUI(BasicOptionUI):
+
+    def __init__(self, option: str, label_text: str, choices: list, orientation='horizontal', **kwargs):
+        self.choices = choices
+        self.orientation = orientation
+        super().__init__(option=option, label_text=label_text, **kwargs)
+
+    def build_entry_widget(self) -> QtWidgets.QWidget:
+        return RadioSet(choices=self.choices, orientation=self.orientation)
+
+
+class TextAreaOptionUI(OptionUI):
+
+    def __init__(self, option: str, label_text: str, label_tooltip: str):
+        super().__init__(option=option)
+        self.label_text = label_text
+        self.label_tooltip = label_tooltip
+        self.label_widget = self.build_label_widget()
+        self.textarea_widget = self.build_textarea_widget()
+
+    def build_label_widget(self):
+        label = QtWidgets.QLabel("%s:" % _(self.label_text))
+        label.setToolTip(_(self.label_tooltip))
+        return label
+
+    def build_textarea_widget(self):
+        textarea = FCTextArea()
+        textarea.setPlaceholderText(_(self.label_tooltip))
+
+        qsettings = QSettings("Open Source", "FlatCAM")
+        if qsettings.contains("textbox_font_size"):
+            tb_fsize = qsettings.value('textbox_font_size', type=int)
+        else:
+            tb_fsize = 10
+        font = QtGui.QFont()
+        font.setPointSize(tb_fsize)
+        textarea.setFont(font)
+
+        return textarea
+
+    def get_field(self):
+        return self.textarea_widget
+
+    def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
+        grid.addWidget(self.label_widget, row, 0, 1, 3)
+        grid.addWidget(self.textarea_widget, row+1, 0, 1, 3)
+        return 2
+
+
+class CheckboxOptionUI(OptionUI):
+
+    def __init__(self, option: str, label_text: str, label_tooltip: str):
+        super().__init__(option=option)
+        self.label_text = label_text
+        self.label_tooltip = label_tooltip
+        self.checkbox_widget = self.build_checkbox_widget()
+
+    def build_checkbox_widget(self):
+        checkbox = FCCheckBox('%s' % _(self.label_text))
+        checkbox.setToolTip(_(self.label_tooltip))
+        return checkbox
+
+    def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
+        grid.addWidget(self.checkbox_widget, row, 0, 1, 3)
+        return 1
+
+    def get_field(self):
+        return self.checkbox_widget
+
+
+class ComboboxOptionUI(BasicOptionUI):
+
+    def __init__(self, option: str, label_text: str, choices: Sequence, **kwargs):
+        self.choices = choices
+        super().__init__(option=option, label_text=label_text, **kwargs)
+
+    def build_entry_widget(self):
+        combo = FCComboBox()
+        for choice in self.choices:
+            # don't translate the QCombo items as they are used in QSettings and identified by name
+            combo.addItem(choice)
+        return combo
+
+
+class ColorOptionUI(BasicOptionUI):
+    def build_entry_widget(self) -> QtWidgets.QWidget:
+        entry = FCColorEntry()
+        return entry
+
+
+class SliderWithSpinnerOptionUI(BasicOptionUI):
+    def __init__(self, option: str, label_text: str, min_value=0, max_value=100, step=1, **kwargs):
+        self.min_value = min_value
+        self.max_value = max_value
+        self.step = step
+        super().__init__(option=option, label_text=label_text, **kwargs)
+
+    def build_entry_widget(self) -> QtWidgets.QWidget:
+        entry = FCSliderWithSpinner(min=self.min_value, max=self.max_value, step=self.step)
+        return entry
+
+
+class ColorAlphaSliderOptionUI(SliderWithSpinnerOptionUI):
+    def __init__(self, applies_to: List[str], group, label_text: str, **kwargs):
+        self.applies_to = applies_to
+        self.group = group
+        super().__init__(option="__color_alpha_slider", label_text=label_text, min_value=0, max_value=255, step=1,
+                         **kwargs)
+        self.get_field().valueChanged.connect(self._on_alpha_change)
+
+    def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
+        for index, field in enumerate(self._get_target_fields()):
+            field.entry.textChanged.connect(lambda value, i=index: self._on_target_change(target_index=i))
+        return super().add_to_grid(grid, row)
+
+    def _get_target_fields(self):
+        return list(map(lambda n: self.group.option_dict()[n].get_field(), self.applies_to))
+
+    def _on_target_change(self, target_index: int):
+        field = self._get_target_fields()[target_index]
+        color = field.get_value()
+        alpha_part = color[7:]
+        if len(alpha_part) != 2:
+            return
+        alpha = int(alpha_part, 16)
+        if alpha < 0 or alpha > 255 or self.get_field().get_value() == alpha:
+            return
+        self.get_field().set_value(alpha)
+
+    def _on_alpha_change(self):
+        alpha = self.get_field().get_value()
+        for field in self._get_target_fields():
+            old_value = field.get_value()
+            new_value = self._modify_color_alpha(old_value, alpha=alpha)
+            field.set_value(new_value)
+
+    @staticmethod
+    def _modify_color_alpha(color: str, alpha: int):
+        color_without_alpha = color[:7]
+        if alpha > 255:
+            return color_without_alpha + "FF"
+        elif alpha < 0:
+            return color_without_alpha + "00"
+        else:
+            hexalpha = hex(alpha)[2:]
+            if len(hexalpha) == 1:
+                hexalpha = "0" + hexalpha
+            return color_without_alpha + hexalpha
+
+
+class SpinnerOptionUI(BasicOptionUI):
+    def __init__(self, option: str, label_text: str, min_value: int, max_value: int, step: int = 1, **kwargs):
+        self.min_value = min_value
+        self.max_value = max_value
+        self.step = step
+        super().__init__(option=option, label_text=label_text, **kwargs)
+
+    def build_entry_widget(self) -> QtWidgets.QWidget:
+        entry = FCSpinner()
+        entry.set_range(self.min_value, self.max_value)
+        entry.set_step(self.step)
+        entry.setWrapping(True)
+        return entry
+
+
+class DoubleSpinnerOptionUI(BasicOptionUI):
+    def __init__(self, option: str, label_text: str, step: float, decimals: int, min_value=None, max_value=None,
+                 suffix=None, **kwargs):
+        self.min_value = min_value
+        self.max_value = max_value
+        self.step = step
+        self.suffix = suffix
+        self.decimals = decimals
+        super().__init__(option=option, label_text=label_text, **kwargs)
+
+    def build_entry_widget(self) -> QtWidgets.QWidget:
+        entry = FCDoubleSpinner(suffix=self.suffix)
+        entry.set_precision(self.decimals)
+        entry.setSingleStep(self.step)
+        if self.min_value is None:
+            self.min_value = entry.minimum()
+        else:
+            entry.setMinimum(self.min_value)
+        if self.max_value is None:
+            self.max_value = entry.maximum()
+        else:
+            entry.setMaximum(self.max_value)
+        return entry
+
+
+class HeadingOptionUI(OptionUI):
+    def __init__(self, label_text: str, label_tooltip: Union[str, None] = None):
+        super().__init__(option="__heading")
+        self.label_text = label_text
+        self.label_tooltip = label_tooltip
+
+    def build_heading_widget(self):
+        heading = QtWidgets.QLabel('<b>%s</b>' % _(self.label_text))
+        heading.setToolTip(_(self.label_tooltip))
+        return heading
+
+    def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
+        grid.addWidget(self.build_heading_widget(), row, 0, 1, 2)
+        return 1
+
+    def get_field(self):
+        return None
+
+
+class SeparatorOptionUI(OptionUI):
+
+    def __init__(self):
+        super().__init__(option="__separator")
+
+    @staticmethod
+    def build_separator_widget():
+        separator = QtWidgets.QFrame()
+        separator.setFrameShape(QtWidgets.QFrame.HLine)
+        separator.setFrameShadow(QtWidgets.QFrame.Sunken)
+        return separator
+
+    def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
+        grid.addWidget(self.build_separator_widget(), row, 0, 1, 2)
+        return 1
+
+    def get_field(self):
+        return None
+
+
+class FullWidthButtonOptionUI(OptionUI):
+    def __init__(self, option: str, label_text: str, label_tooltip: Union[str, None]):
+        super().__init__(option=option)
+        self.label_text = label_text
+        self.label_tooltip = label_tooltip
+        self.button_widget = self.build_button_widget()
+
+    def build_button_widget(self):
+        button = FCButton(_(self.label_text))
+        if self.label_tooltip is not None:
+            button.setToolTip(_(self.label_tooltip))
+        return button
+
+    def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
+        grid.addWidget(self.button_widget, row, 0, 1, 3)
+        return 1
+
+    def get_field(self):
+        return self.button_widget

+ 77 - 0
AppGUI/preferences/OptionsGroupUI.py

@@ -0,0 +1,77 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# File by:  David Robertson (c)                            #
+# Date:     5/2020                                         #
+# License:  MIT Licence                                    #
+# ##########################################################
+
+from typing import Dict
+
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import QSettings
+
+import gettext
+import AppTranslation as fcTranslate
+import builtins
+
+from AppGUI.preferences.OptionUI import OptionUI
+
+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 OptionsGroupUI(QtWidgets.QGroupBox):
+    app = None
+
+    def __init__(self, title, parent=None):
+        # QtGui.QGroupBox.__init__(self, title, parent=parent)
+        super(OptionsGroupUI, self).__init__()
+        self.setStyleSheet("""
+        QGroupBox
+        {
+            font-size: 16px;
+            font-weight: bold;
+        }
+        """)
+
+        self.layout = QtWidgets.QVBoxLayout()
+        self.setLayout(self.layout)
+
+    def option_dict(self) -> Dict[str, OptionUI]:
+        # FIXME!
+        return {}
+
+
+class OptionsGroupUI2(OptionsGroupUI):
+
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+
+        self.grid = QtWidgets.QGridLayout()
+        self.layout.addLayout(self.grid)
+        self.grid.setColumnStretch(0, 0)
+        self.grid.setColumnStretch(1, 1)
+
+        self.options = self.build_options()
+
+        row = 0
+        for option in self.options:
+            row += option.add_to_grid(grid=self.grid, row=row)
+
+        self.layout.addStretch()
+
+    def build_options(self) -> [OptionUI]:
+        return []
+
+    def option_dict(self) -> Dict[str, OptionUI]:
+        result = {}
+        for optionui in self.options:
+            result[optionui.option] = optionui
+        return result

+ 41 - 0
AppGUI/preferences/PreferencesSectionUI.py

@@ -0,0 +1,41 @@
+from typing import Dict
+from PyQt5 import QtWidgets, QtCore
+
+from AppGUI.ColumnarFlowLayout import ColumnarFlowLayout
+from AppGUI.preferences.OptionUI import OptionUI
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+
+
+class PreferencesSectionUI(QtWidgets.QWidget):
+
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+        self.layout = ColumnarFlowLayout()  # QtWidgets.QHBoxLayout()
+        self.setLayout(self.layout)
+
+        self.groups = self.build_groups()
+        for group in self.groups:
+            group.setMinimumWidth(250)
+            self.layout.addWidget(group)
+
+    def build_groups(self) -> [OptionsGroupUI]:
+        return []
+
+    def option_dict(self) -> Dict[str, OptionUI]:
+        result = {}
+        for group in self.groups:
+            groupoptions = group.option_dict()
+            result.update(groupoptions)
+        return result
+
+    def build_tab(self):
+        scroll_area = QtWidgets.QScrollArea()
+        scroll_area.setWidget(self)
+        scroll_area.setWidgetResizable(True)
+        return scroll_area
+
+    def get_tab_id(self) -> str:
+        raise NotImplementedError
+
+    def get_tab_label(self) -> str:
+        raise NotImplementedError

+ 184 - 218
flatcamGUI/preferences/PreferencesUIManager.py → AppGUI/preferences/PreferencesUIManager.py

@@ -5,7 +5,7 @@ from defaults import FlatCAMDefaults
 import logging
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -29,7 +29,7 @@ class PreferencesUIManager:
 
         :param defaults:    a dictionary storage where all the application settings are stored
         :param data_path:   a path to the file where all the preferences are stored for persistence
-        :param ui:          reference to the FlatCAMGUI class which constructs the UI
+        :param ui:          reference to the MainGUI class which constructs the UI
         :param inform:      a pyqtSignal used to display information's in the StatusBar of the GUI
         """
 
@@ -43,7 +43,7 @@ class PreferencesUIManager:
         self.preferences_changed_flag = False
 
         # when adding entries here read the comments in the  method found below named:
-        # def new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
+        # def app_obj.new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
         self.defaults_form_fields = {
             # General App
             "decimals_inch": self.ui.general_defaults_form.general_app_group.precision_inch_entry,
@@ -123,16 +123,10 @@ class PreferencesUIManager:
             "gerber_def_zeros": self.ui.gerber_defaults_form.gerber_gen_group.gerber_zeros_radio,
             "gerber_clean_apertures": self.ui.gerber_defaults_form.gerber_gen_group.gerber_clean_cb,
             "gerber_extra_buffering": self.ui.gerber_defaults_form.gerber_gen_group.gerber_extra_buffering,
-            "gerber_plot_fill": self.ui.gerber_defaults_form.gerber_gen_group.pf_color_entry,
-            "gerber_plot_line": self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry,
+            "gerber_plot_fill": self.ui.gerber_defaults_form.gerber_gen_group.fill_color_entry,
+            "gerber_plot_line": self.ui.gerber_defaults_form.gerber_gen_group.line_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.iso_scope_radio,
-            "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,
@@ -467,6 +479,12 @@ class PreferencesUIManager:
             "tools_solderpaste_pp": self.ui.tools_defaults_form.tools_solderpaste_group.pp_combo,
             "tools_sub_close_paths": self.ui.tools_defaults_form.tools_sub_group.close_paths_cb,
 
+            # Corner Markers Tool
+
+            "tools_corners_thickness": self.ui.tools_defaults_form.tools_corners_group.thick_entry,
+            "tools_corners_length": self.ui.tools_defaults_form.tools_corners_group.l_entry,
+            "tools_corners_margin": self.ui.tools_defaults_form.tools_corners_group.margin_entry,
+
             # #######################################################################################################
             # ########################################## TOOLS 2 ####################################################
             # #######################################################################################################
@@ -650,7 +668,7 @@ class PreferencesUIManager:
 
     def show_preferences_gui(self):
         """
-        Called to initialize and show the Preferences GUI
+        Called to initialize and show the Preferences AppGUI
 
         :return: None
         """
@@ -719,7 +737,7 @@ class PreferencesUIManager:
         self.ui.fa_scroll_area.setWidget(fa_form)
         fa_form.show()
 
-        # Initialize the color box's color in Preferences -> Global -> Colo
+        # Initialize the color box's color in Preferences -> Global -> Colors
         self.__init_color_pickers()
 
         # Button handlers
@@ -732,167 +750,90 @@ class PreferencesUIManager:
 
     def __init_color_pickers(self):
         # Init Gerber Plot Colors
-        self.ui.gerber_defaults_form.gerber_gen_group.pf_color_entry.set_value(self.defaults['gerber_plot_fill'])
-        self.ui.gerber_defaults_form.gerber_gen_group.pf_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['gerber_plot_fill'])[:7])
-        self.ui.gerber_defaults_form.gerber_gen_group.pf_color_alpha_spinner.set_value(
-            int(self.defaults['gerber_plot_fill'][7:9], 16))
-        self.ui.gerber_defaults_form.gerber_gen_group.pf_color_alpha_slider.setValue(
-            int(self.defaults['gerber_plot_fill'][7:9], 16))
-
-        self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry.set_value(self.defaults['gerber_plot_line'])
-        self.ui.gerber_defaults_form.gerber_gen_group.pl_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['gerber_plot_line'])[:7])
+        self.ui.gerber_defaults_form.gerber_gen_group.fill_color_entry.set_value(self.defaults['gerber_plot_fill'])
+        self.ui.gerber_defaults_form.gerber_gen_group.line_color_entry.set_value(self.defaults['gerber_plot_line'])
+
+        self.ui.gerber_defaults_form.gerber_gen_group.gerber_alpha_entry.set_value(
+            int(self.defaults['gerber_plot_fill'][7:9], 16))    # alpha
 
         # Init Excellon Plot Colors
         self.ui.excellon_defaults_form.excellon_gen_group.fill_color_entry.set_value(
             self.defaults['excellon_plot_fill'])
-        self.ui.excellon_defaults_form.excellon_gen_group.fill_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['excellon_plot_fill'])[:7])
-        self.ui.excellon_defaults_form.excellon_gen_group.color_alpha_spinner.set_value(
-            int(self.defaults['excellon_plot_fill'][7:9], 16))
-        self.ui.excellon_defaults_form.excellon_gen_group.color_alpha_slider.setValue(
-            int(self.defaults['excellon_plot_fill'][7:9], 16))
-
         self.ui.excellon_defaults_form.excellon_gen_group.line_color_entry.set_value(
             self.defaults['excellon_plot_line'])
-        self.ui.excellon_defaults_form.excellon_gen_group.line_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['excellon_plot_line'])[:7])
+
+        self.ui.excellon_defaults_form.excellon_gen_group.excellon_alpha_entry.set_value(
+            int(self.defaults['excellon_plot_fill'][7:9], 16))
 
         # Init Geometry Plot Colors
         self.ui.geometry_defaults_form.geometry_gen_group.line_color_entry.set_value(
             self.defaults['geometry_plot_line'])
-        self.ui.geometry_defaults_form.geometry_gen_group.line_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['geometry_plot_line'])[:7])
 
         # Init CNCJob Travel Line Colors
         self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_entry.set_value(
             self.defaults['cncjob_travel_fill'])
-        self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['cncjob_travel_fill'])[:7])
-        self.ui.cncjob_defaults_form.cncjob_gen_group.tcolor_alpha_spinner.set_value(
-            int(self.defaults['cncjob_travel_fill'][7:9], 16))
-        self.ui.cncjob_defaults_form.cncjob_gen_group.tcolor_alpha_slider.setValue(
-            int(self.defaults['cncjob_travel_fill'][7:9], 16))
-
         self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_entry.set_value(
             self.defaults['cncjob_travel_line'])
-        self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['cncjob_travel_line'])[:7])
+
+        self.ui.cncjob_defaults_form.cncjob_gen_group.cncjob_alpha_entry.set_value(
+            int(self.defaults['cncjob_travel_fill'][7:9], 16))      # alpha
 
         # Init CNCJob Plot Colors
         self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_entry.set_value(
             self.defaults['cncjob_plot_fill'])
-        self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['cncjob_plot_fill'])[:7])
 
         self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_entry.set_value(
             self.defaults['cncjob_plot_line'])
-        self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['cncjob_plot_line'])[:7])
 
         # Init Left-Right Selection colors
         self.ui.general_defaults_form.general_gui_group.sf_color_entry.set_value(self.defaults['global_sel_fill'])
-        self.ui.general_defaults_form.general_gui_group.sf_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['global_sel_fill'])[:7])
-        self.ui.general_defaults_form.general_gui_group.sf_color_alpha_spinner.set_value(
-            int(self.defaults['global_sel_fill'][7:9], 16))
-        self.ui.general_defaults_form.general_gui_group.sf_color_alpha_slider.setValue(
-            int(self.defaults['global_sel_fill'][7:9], 16))
-
         self.ui.general_defaults_form.general_gui_group.sl_color_entry.set_value(self.defaults['global_sel_line'])
-        self.ui.general_defaults_form.general_gui_group.sl_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['global_sel_line'])[:7])
+
+        self.ui.general_defaults_form.general_gui_group.left_right_alpha_entry.set_value(
+            int(self.defaults['global_sel_fill'][7:9], 16))
 
         # Init Right-Left Selection colors
         self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.set_value(
             self.defaults['global_alt_sel_fill'])
-        self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['global_alt_sel_fill'])[:7])
-        self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_spinner.set_value(
-            int(self.defaults['global_sel_fill'][7:9], 16))
-        self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_slider.setValue(
-            int(self.defaults['global_sel_fill'][7:9], 16))
-
         self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.set_value(
             self.defaults['global_alt_sel_line'])
-        self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['global_alt_sel_line'])[:7])
+
+        self.ui.general_defaults_form.general_gui_group.right_left_alpha_entry.set_value(
+            int(self.defaults['global_sel_fill'][7:9], 16))
 
         # Init Draw color and Selection Draw Color
         self.ui.general_defaults_form.general_gui_group.draw_color_entry.set_value(
             self.defaults['global_draw_color'])
-        self.ui.general_defaults_form.general_gui_group.draw_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['global_draw_color'])[:7])
 
         self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.set_value(
             self.defaults['global_sel_draw_color'])
-        self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['global_sel_draw_color'])[:7])
 
         # Init Project Items color
         self.ui.general_defaults_form.general_gui_group.proj_color_entry.set_value(
             self.defaults['global_proj_item_color'])
-        self.ui.general_defaults_form.general_gui_group.proj_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['global_proj_item_color'])[:7])
 
         # Init Project Disabled Items color
         self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry.set_value(
             self.defaults['global_proj_item_dis_color'])
-        self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['global_proj_item_dis_color'])[:7])
 
-        # Init Project Disabled Items color
+        # Init Mouse Cursor color
         self.ui.general_defaults_form.general_app_set_group.mouse_cursor_entry.set_value(
             self.defaults['global_cursor_color'])
-        self.ui.general_defaults_form.general_app_set_group.mouse_cursor_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['global_cursor_color'])[:7])
 
         # Init the Annotation CNC Job color
         self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry.set_value(
             self.defaults['cncjob_annotation_fontcolor'])
-        self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['cncjob_annotation_fontcolor'])[:7])
 
         # Init the Tool Film color
         self.ui.tools_defaults_form.tools_film_group.film_color_entry.set_value(
             self.defaults['tools_film_color'])
-        self.ui.tools_defaults_form.tools_film_group.film_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['tools_film_color'])[:7]
-        )
 
         # Init the Tool QRCode colors
         self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry.set_value(
             self.defaults['tools_qrcode_fill_color'])
-        self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['tools_qrcode_fill_color'])[:7])
 
         self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry.set_value(
             self.defaults['tools_qrcode_back_color'])
-        self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_button.setStyleSheet(
-            "background-color:%s;"
-            "border-color: dimgray" % str(self.defaults['tools_qrcode_back_color'])[:7])
 
     def on_save_button(self, save_to_file=True):
         log.debug("on_save_button() --> Applying preferences to file.")
@@ -921,12 +862,17 @@ class PreferencesUIManager:
             theme = 'white'
 
         should_restart = False
-        val = self.ui.general_defaults_form.general_gui_group.theme_radio.get_value()
-        if val != theme:
+        theme_new_val = self.ui.general_defaults_form.general_gui_group.theme_radio.get_value()
+
+        ge = self.defaults["global_graphic_engine"]
+        ge_val = self.ui.general_defaults_form.general_app_group.ge_radio.get_value()
+
+        if theme_new_val != theme or ge != ge_val:
             msgbox = QtWidgets.QMessageBox()
             msgbox.setText(_("Are you sure you want to continue?"))
-            msgbox.setWindowTitle(_("Application restart"))
+            msgbox.setWindowTitle(_("Application will 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)
@@ -935,15 +881,22 @@ class PreferencesUIManager:
             msgbox.exec_()
             response = msgbox.clickedButton()
 
-            if response == bt_yes:
-                theme_settings.setValue('theme', val)
+            if theme_new_val != theme:
+                if response == bt_yes:
+                    theme_settings.setValue('theme', theme_new_val)
 
-                # This will write the setting to the platform specific storage.
-                del theme_settings
+                    # This will write the setting to the platform specific storage.
+                    del theme_settings
 
-                should_restart = True
+                    should_restart = True
+                else:
+                    self.ui.general_defaults_form.general_gui_group.theme_radio.set_value(theme)
             else:
-                self.ui.general_defaults_form.general_gui_group.theme_radio.set_value(theme)
+                if response == bt_yes:
+                    self.defaults["global_graphic_engine"] = ge_val
+                    should_restart = True
+                else:
+                    self.ui.general_defaults_form.general_app_group.ge_radio.set_value(ge)
 
         if save_to_file or should_restart is True:
             self.save_defaults(silent=False)
@@ -967,6 +920,10 @@ class PreferencesUIManager:
         tb_fsize = self.ui.general_defaults_form.general_app_set_group.textbox_font_size_spinner.get_value()
         settgs.setValue('textbox_font_size', tb_fsize)
 
+        # save the HUD font size
+        hud_fsize = self.ui.general_defaults_form.general_app_set_group.hud_font_size_spinner.get_value()
+        settgs.setValue('hud_font_size', hud_fsize)
+
         settgs.setValue(
             'machinist',
             1 if self.ui.general_defaults_form.general_app_set_group.machinist_cb.get_value() else 0
@@ -991,10 +948,14 @@ class PreferencesUIManager:
         self.preferences_changed_flag = False
         self.ignore_tab_close_event = True
 
+        # restore stylesheet to default for the statusBar icon
+        self.ui.pref_status_label.setStyleSheet("")
+
         try:
             self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.disconnect()
         except (TypeError, AttributeError):
             pass
+
         self.defaults_write_form(source_dict=self.defaults.current_defaults)
         self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(
             lambda: self.ui.app.on_toggle_units(no_pref=False))
@@ -1035,6 +996,7 @@ class PreferencesUIManager:
         :return:            None
         """
         self.defaults.report_usage("save_defaults")
+        log.debug("App.PreferencesUIManager.save_defaults()")
 
         if data_path is None:
             data_path = self.data_path
@@ -1071,7 +1033,7 @@ class PreferencesUIManager:
         if self.ui.toolbarfile.isVisible():
             tb_status += 1
 
-        if self.ui.toolbargeo.isVisible():
+        if self.ui.toolbaredit.isVisible():
             tb_status += 2
 
         if self.ui.toolbarview.isVisible():
@@ -1089,7 +1051,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():
@@ -1118,6 +1080,9 @@ class PreferencesUIManager:
         if self.ignore_tab_close_event:
             return
 
+        # restore stylesheet to default for the statusBar icon
+        self.ui.pref_status_label.setStyleSheet("")
+
         # disconnect
         for idx in range(self.ui.pref_tab_area.count()):
             for tb in self.ui.pref_tab_area.widget(idx).findChildren(QtCore.QObject):
@@ -1153,6 +1118,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)

+ 2 - 2
flatcamGUI/preferences/__init__.py → AppGUI/preferences/__init__.py

@@ -1,6 +1,6 @@
-from flatcamGUI.GUIElements import *
+from AppGUI.GUIElements import *
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 

+ 8 - 37
flatcamGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py → AppGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py

@@ -1,10 +1,10 @@
-from PyQt5 import QtWidgets, QtGui, QtCore
+from PyQt5 import QtWidgets, QtGui
 from PyQt5.QtCore import QSettings, Qt
 
-from flatcamGUI.GUIElements import FCTextArea, FCCheckBox, FCComboBox, FCSpinner, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCTextArea, FCCheckBox, FCComboBox, FCSpinner, FCColorEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -158,28 +158,16 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
         self.annotation_color_label.setToolTip(
             _("Set the font color for the annotation texts.")
         )
-        self.annotation_fontcolor_entry = FCEntry()
-        self.annotation_fontcolor_button = QtWidgets.QPushButton()
-        self.annotation_fontcolor_button.setFixedSize(15, 15)
-
-        self.form_box_child = QtWidgets.QHBoxLayout()
-        self.form_box_child.setContentsMargins(0, 0, 0, 0)
-        self.form_box_child.addWidget(self.annotation_fontcolor_entry)
-        self.form_box_child.addWidget(self.annotation_fontcolor_button, alignment=Qt.AlignRight)
-        self.form_box_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        color_widget = QtWidgets.QWidget()
-        color_widget.setLayout(self.form_box_child)
+        self.annotation_fontcolor_entry = FCColorEntry()
+
         grid0.addWidget(self.annotation_color_label, 3, 0)
-        grid0.addWidget(color_widget, 3, 1)
-        grid0.addWidget(QtWidgets.QLabel(''), 3, 2)
+        grid0.addWidget(self.annotation_fontcolor_entry, 3, 1)
 
+        grid0.addWidget(QtWidgets.QLabel(''), 3, 2)
         self.layout.addStretch()
 
         self.tc_variable_combo.currentIndexChanged[str].connect(self.on_cnc_custom_parameters)
-
         self.annotation_fontcolor_entry.editingFinished.connect(self.on_annotation_fontcolor_entry)
-        self.annotation_fontcolor_button.clicked.connect(self.on_annotation_fontcolor_button)
 
     def on_cnc_custom_parameters(self, signal_text):
         if signal_text == 'Parameters':
@@ -189,20 +177,3 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
 
     def on_annotation_fontcolor_entry(self):
         self.app.defaults['cncjob_annotation_fontcolor'] = self.annotation_fontcolor_entry.get_value()
-        self.annotation_fontcolor_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['cncjob_annotation_fontcolor']))
-
-    def on_annotation_fontcolor_button(self):
-        current_color = QtGui.QColor(self.app.defaults['cncjob_annotation_fontcolor'])
-
-        c_dialog = QtWidgets.QColorDialog()
-        annotation_color = c_dialog.getColor(initial=current_color)
-
-        if annotation_color.isValid() is False:
-            return
-
-        self.annotation_fontcolor_button.setStyleSheet("background-color:%s" % str(annotation_color.name()))
-
-        new_val_sel = str(annotation_color.name())
-        self.annotation_fontcolor_entry.set_value(new_val_sel)
-        self.app.defaults['cncjob_annotation_fontcolor'] = new_val_sel

+ 23 - 142
flatcamGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py → AppGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py

@@ -1,10 +1,10 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCSpinner, FCDoubleSpinner, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCCheckBox, RadioSet, FCSpinner, FCDoubleSpinner, FCSliderWithSpinner, FCColorEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -170,17 +170,10 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
         self.tline_color_label.setToolTip(
             _("Set the travel line color for plotted objects.")
         )
-        self.tline_color_entry = FCEntry()
-        self.tline_color_button = QtWidgets.QPushButton()
-        self.tline_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_2 = QtWidgets.QHBoxLayout()
-        self.form_box_child_2.addWidget(self.tline_color_entry)
-        self.form_box_child_2.addWidget(self.tline_color_button)
-        self.form_box_child_2.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.tline_color_entry = FCColorEntry()
 
         grid0.addWidget(self.tline_color_label, 14, 0)
-        grid0.addLayout(self.form_box_child_2, 14, 1)
+        grid0.addWidget(self.tline_color_entry, 14, 1)
 
         # Plot Fill Color
         self.tfill_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
@@ -189,38 +182,20 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.tfill_color_entry = FCEntry()
-        self.tfill_color_button = QtWidgets.QPushButton()
-        self.tfill_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_1 = QtWidgets.QHBoxLayout()
-        self.form_box_child_1.addWidget(self.tfill_color_entry)
-        self.form_box_child_1.addWidget(self.tfill_color_button)
-        self.form_box_child_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.tfill_color_entry = FCColorEntry()
 
         grid0.addWidget(self.tfill_color_label, 15, 0)
-        grid0.addLayout(self.form_box_child_1, 15, 1)
+        grid0.addWidget(self.tfill_color_entry, 15, 1)
 
         # Plot Fill Transparency Level
-        self.alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
-        self.alpha_label.setToolTip(
+        self.cncjob_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
+        self.cncjob_alpha_label.setToolTip(
             _("Set the fill transparency for plotted objects.")
         )
-        self.tcolor_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
-        self.tcolor_alpha_slider.setMinimum(0)
-        self.tcolor_alpha_slider.setMaximum(255)
-        self.tcolor_alpha_slider.setSingleStep(1)
-
-        self.tcolor_alpha_spinner = FCSpinner()
-        self.tcolor_alpha_spinner.setMinimumWidth(70)
-        self.tcolor_alpha_spinner.set_range(0, 255)
+        self.cncjob_alpha_entry = FCSliderWithSpinner(0, 255, 1)
 
-        self.form_box_child_3 = QtWidgets.QHBoxLayout()
-        self.form_box_child_3.addWidget(self.tcolor_alpha_slider)
-        self.form_box_child_3.addWidget(self.tcolor_alpha_spinner)
-
-        grid0.addWidget(self.alpha_label, 16, 0)
-        grid0.addLayout(self.form_box_child_3, 16, 1)
+        grid0.addWidget(self.cncjob_alpha_label, 16, 0)
+        grid0.addWidget(self.cncjob_alpha_entry, 16, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -228,7 +203,7 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(separator_line, 17, 0, 1, 2)
 
         # CNCJob Object Color
-        self.cnc_color_label = QtWidgets.QLabel('<b>%s</b>' % _('CNCJob Object Color'))
+        self.cnc_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Object Color'))
         grid0.addWidget(self.cnc_color_label, 18, 0, 1, 2)
 
         # Plot Line Color
@@ -236,17 +211,10 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
         self.line_color_label.setToolTip(
             _("Set the color for plotted objects.")
         )
-        self.line_color_entry = FCEntry()
-        self.line_color_button = QtWidgets.QPushButton()
-        self.line_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_2 = QtWidgets.QHBoxLayout()
-        self.form_box_child_2.addWidget(self.line_color_entry)
-        self.form_box_child_2.addWidget(self.line_color_button)
-        self.form_box_child_2.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.line_color_entry = FCColorEntry()
 
         grid0.addWidget(self.line_color_label, 19, 0)
-        grid0.addLayout(self.form_box_child_2, 19, 1)
+        grid0.addWidget(self.line_color_entry, 19, 1)
 
         # Plot Fill Color
         self.fill_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
@@ -255,32 +223,21 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.fill_color_entry = FCEntry()
-        self.fill_color_button = QtWidgets.QPushButton()
-        self.fill_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_1 = QtWidgets.QHBoxLayout()
-        self.form_box_child_1.addWidget(self.fill_color_entry)
-        self.form_box_child_1.addWidget(self.fill_color_button)
-        self.form_box_child_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.fill_color_entry = FCColorEntry()
 
         grid0.addWidget(self.fill_color_label, 20, 0)
-        grid0.addLayout(self.form_box_child_1, 20, 1)
+        grid0.addWidget(self.fill_color_entry, 20, 1)
 
         self.layout.addStretch()
 
         # Setting plot colors signals
         self.tline_color_entry.editingFinished.connect(self.on_tline_color_entry)
-        self.tline_color_button.clicked.connect(self.on_tline_color_button)
         self.tfill_color_entry.editingFinished.connect(self.on_tfill_color_entry)
-        self.tfill_color_button.clicked.connect(self.on_tfill_color_button)
-        self.tcolor_alpha_spinner.valueChanged.connect(self.on_tcolor_spinner)
-        self.tcolor_alpha_slider.valueChanged.connect(self.on_tcolor_slider)
+
+        self.cncjob_alpha_entry.valueChanged.connect(self.on_cncjob_alpha_changed)  # alpha
 
         self.line_color_entry.editingFinished.connect(self.on_line_color_entry)
-        self.line_color_button.clicked.connect(self.on_line_color_button)
         self.fill_color_entry.editingFinished.connect(self.on_fill_color_entry)
-        self.fill_color_button.clicked.connect(self.on_fill_color_button)
 
     # ------------------------------------------------------
     # Setting travel colors handlers
@@ -288,27 +245,12 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
     def on_tfill_color_entry(self):
         self.app.defaults['cncjob_travel_fill'] = self.tfill_color_entry.get_value()[:7] + \
                                                   self.app.defaults['cncjob_travel_fill'][7:9]
-        self.tfill_color_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['cncjob_travel_fill'])[:7])
-
-    def on_tfill_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['cncjob_travel_fill'][:7])
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_fill_color = c_dialog.getColor(initial=current_color)
 
-        if plot_fill_color.isValid() is False:
-            return
-
-        self.tfill_color_button.setStyleSheet("background-color:%s" % str(plot_fill_color.name()))
-
-        new_val = str(plot_fill_color.name()) + str(self.app.defaults['cncjob_travel_fill'][7:9])
-        self.tfill_color_entry.set_value(new_val)
-        self.app.defaults['cncjob_travel_fill'] = new_val
+    def on_tline_color_entry(self):
+        self.app.defaults['cncjob_travel_line'] = self.tline_color_entry.get_value()[:7] + \
+                                                  self.app.defaults['cncjob_travel_line'][7:9]
 
-    def on_tcolor_spinner(self):
-        spinner_value = self.tcolor_alpha_spinner.value()
-        self.tcolor_alpha_slider.setValue(spinner_value)
+    def on_cncjob_alpha_changed(self, spinner_value):
         self.app.defaults['cncjob_travel_fill'] = \
             self.app.defaults['cncjob_travel_fill'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
@@ -316,74 +258,13 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
             self.app.defaults['cncjob_travel_line'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
 
-    def on_tcolor_slider(self):
-        slider_value = self.tcolor_alpha_slider.value()
-        self.tcolor_alpha_spinner.setValue(slider_value)
-
-    def on_tline_color_entry(self):
-        self.app.defaults['cncjob_travel_line'] = self.tline_color_entry.get_value()[:7] + \
-                                                  self.app.defaults['cncjob_travel_line'][7:9]
-        self.tline_color_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['cncjob_travel_line'])[:7])
-
-    def on_tline_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['cncjob_travel_line'][:7])
-        # print(current_color)
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_line_color = c_dialog.getColor(initial=current_color)
-
-        if plot_line_color.isValid() is False:
-            return
-
-        self.tline_color_button.setStyleSheet("background-color:%s" % str(plot_line_color.name()))
-
-        new_val_line = str(plot_line_color.name()) + str(self.app.defaults['cncjob_travel_line'][7:9])
-        self.tline_color_entry.set_value(new_val_line)
-        self.app.defaults['cncjob_travel_line'] = new_val_line
-
     # ------------------------------------------------------
     # Setting plot colors handlers
     # ------------------------------------------------------
     def on_fill_color_entry(self):
         self.app.defaults['cncjob_plot_fill'] = self.fill_color_entry.get_value()[:7] + \
                                                   self.app.defaults['cncjob_plot_fill'][7:9]
-        self.fill_color_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['cncjob_plot_fill'])[:7])
-
-    def on_fill_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['cncjob_plot_fill'][:7])
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_fill_color = c_dialog.getColor(initial=current_color)
-
-        if plot_fill_color.isValid() is False:
-            return
-
-        self.fill_color_button.setStyleSheet("background-color:%s" % str(plot_fill_color.name()))
-
-        new_val = str(plot_fill_color.name()) + str(self.app.defaults['cncjob_plot_fill'][7:9])
-        self.fill_color_entry.set_value(new_val)
-        self.app.defaults['cncjob_plot_fill'] = new_val
 
     def on_line_color_entry(self):
         self.app.defaults['cncjob_plot_line'] = self.line_color_entry.get_value()[:7] + \
                                                   self.app.defaults['cncjob_plot_line'][7:9]
-        self.line_color_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['cncjob_plot_line'])[:7])
-
-    def on_line_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['cncjob_plot_line'][:7])
-        # print(current_color)
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_line_color = c_dialog.getColor(initial=current_color)
-
-        if plot_line_color.isValid() is False:
-            return
-
-        self.line_color_button.setStyleSheet("background-color:%s" % str(plot_line_color.name()))
-
-        new_val_line = str(plot_line_color.name()) + str(self.app.defaults['cncjob_plot_line'][7:9])
-        self.line_color_entry.set_value(new_val_line)
-        self.app.defaults['cncjob_plot_line'] = new_val_line

+ 3 - 3
flatcamGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py → AppGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets, QtGui
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCTextArea
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCTextArea
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/cncjob/CNCJobPreferencesUI.py → AppGUI/preferences/cncjob/CNCJobPreferencesUI.py

@@ -1,8 +1,8 @@
 from PyQt5 import QtWidgets
 
-from flatcamGUI.preferences.cncjob.CNCJobAdvOptPrefGroupUI import CNCJobAdvOptPrefGroupUI
-from flatcamGUI.preferences.cncjob.CNCJobOptPrefGroupUI import CNCJobOptPrefGroupUI
-from flatcamGUI.preferences.cncjob.CNCJobGenPrefGroupUI import CNCJobGenPrefGroupUI
+from AppGUI.preferences.cncjob.CNCJobAdvOptPrefGroupUI import CNCJobAdvOptPrefGroupUI
+from AppGUI.preferences.cncjob.CNCJobOptPrefGroupUI import CNCJobOptPrefGroupUI
+from AppGUI.preferences.cncjob.CNCJobGenPrefGroupUI import CNCJobGenPrefGroupUI
 
 
 class CNCJobPreferencesUI(QtWidgets.QWidget):

+ 0 - 0
flatcamGUI/preferences/cncjob/__init__.py → AppGUI/preferences/cncjob/__init__.py


+ 5 - 5
flatcamGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py → AppGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py

@@ -1,10 +1,10 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner, FCEntry, FloatEntry, RadioSet, FCCheckBox
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, RadioSet, FCCheckBox, NumericalEvalTupleEntry, NumericalEvalEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)

+ 3 - 3
flatcamGUI/preferences/excellon/ExcellonEditorPrefGroupUI.py → AppGUI/preferences/excellon/ExcellonEditorPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/excellon/ExcellonExpPrefGroupUI.py → AppGUI/preferences/excellon/ExcellonExpPrefGroupUI.py

@@ -1,10 +1,10 @@
 from PyQt5 import QtWidgets, QtCore
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import RadioSet, FCSpinner
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import RadioSet, FCSpinner
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 137 - 86
flatcamGUI/preferences/excellon/ExcellonGenPrefGroupUI.py → AppGUI/preferences/excellon/ExcellonGenPrefGroupUI.py

@@ -3,10 +3,10 @@ import platform
 from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry, FCSliderWithSpinner, FCColorEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)
@@ -255,7 +264,7 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
         grid2.addWidget(separator_line, 11, 0, 1, 2)
 
         # Excellon Object Color
-        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Excellon Object Color'))
+        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Object Color'))
         grid2.addWidget(self.gerber_color_label, 12, 0, 1, 2)
 
         # Plot Line Color
@@ -263,17 +272,10 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
         self.line_color_label.setToolTip(
             _("Set the line color for plotted objects.")
         )
-        self.line_color_entry = FCEntry()
-        self.line_color_button = QtWidgets.QPushButton()
-        self.line_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_2 = QtWidgets.QHBoxLayout()
-        self.form_box_child_2.addWidget(self.line_color_entry)
-        self.form_box_child_2.addWidget(self.line_color_button)
-        self.form_box_child_2.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.line_color_entry = FCColorEntry()
 
         grid2.addWidget(self.line_color_label, 13, 0)
-        grid2.addLayout(self.form_box_child_2, 13, 1)
+        grid2.addWidget(self.line_color_entry, 13, 1)
 
         # Plot Fill Color
         self.fill_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
@@ -282,38 +284,20 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.fill_color_entry = FCEntry()
-        self.fill_color_button = QtWidgets.QPushButton()
-        self.fill_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_1 = QtWidgets.QHBoxLayout()
-        self.form_box_child_1.addWidget(self.fill_color_entry)
-        self.form_box_child_1.addWidget(self.fill_color_button)
-        self.form_box_child_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.fill_color_entry = FCColorEntry()
 
         grid2.addWidget(self.fill_color_label, 14, 0)
-        grid2.addLayout(self.form_box_child_1, 14, 1)
+        grid2.addWidget(self.fill_color_entry, 14, 1)
 
         # Plot Fill Transparency Level
-        self.alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
-        self.alpha_label.setToolTip(
+        self.excellon_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
+        self.excellon_alpha_label.setToolTip(
             _("Set the fill transparency for plotted objects.")
         )
-        self.color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
-        self.color_alpha_slider.setMinimum(0)
-        self.color_alpha_slider.setMaximum(255)
-        self.color_alpha_slider.setSingleStep(1)
+        self.excellon_alpha_entry = FCSliderWithSpinner(0, 255, 1)
 
-        self.color_alpha_spinner = FCSpinner()
-        self.color_alpha_spinner.setMinimumWidth(70)
-        self.color_alpha_spinner.set_range(0, 255)
-
-        self.form_box_child_3 = QtWidgets.QHBoxLayout()
-        self.form_box_child_3.addWidget(self.color_alpha_slider)
-        self.form_box_child_3.addWidget(self.color_alpha_spinner)
-
-        grid2.addWidget(self.alpha_label, 15, 0)
-        grid2.addLayout(self.form_box_child_3, 15, 1)
+        grid2.addWidget(self.excellon_alpha_label, 15, 0)
+        grid2.addWidget(self.excellon_alpha_entry, 15, 1)
 
         self.layout.addStretch()
 
@@ -333,14 +317,18 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
 
         # Setting plot colors signals
         self.line_color_entry.editingFinished.connect(self.on_line_color_entry)
-        self.line_color_button.clicked.connect(self.on_line_color_button)
         self.fill_color_entry.editingFinished.connect(self.on_fill_color_entry)
-        self.fill_color_button.clicked.connect(self.on_fill_color_button)
-        self.color_alpha_spinner.valueChanged.connect(self.on_color_spinner)
-        self.color_alpha_slider.valueChanged.connect(self.on_color_slider)
+
+        self.excellon_alpha_entry.valueChanged.connect(self.on_excellon_alpha_changed)  # alpha
 
         # Load the defaults values into the Excellon Format and Excellon Zeros fields
         self.excellon_defaults_button.clicked.connect(self.on_excellon_defaults_button)
+        # Make sure that when the Excellon loading parameters are changed, the change is reflected in the
+        # Export Excellon parameters.
+        self.update_excellon_cb.stateChanged.connect(self.on_update_exc_export)
+
+        # call it once to make sure it is updated at startup
+        self.on_update_exc_export(state=self.app.defaults["excellon_update"])
 
     def optimization_selection(self):
         if self.excellon_optimization_radio.get_value() == 'M':
@@ -354,26 +342,12 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
     def on_fill_color_entry(self):
         self.app.defaults['excellon_plot_fill'] = self.fill_color_entry.get_value()[:7] + \
             self.app.defaults['excellon_plot_fill'][7:9]
-        self.fill_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['excellon_plot_fill'])[:7])
-
-    def on_fill_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['excellon_plot_fill'][:7])
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_fill_color = c_dialog.getColor(initial=current_color)
-
-        if plot_fill_color.isValid() is False:
-            return
 
-        self.fill_color_button.setStyleSheet("background-color:%s" % str(plot_fill_color.name()))
-
-        new_val = str(plot_fill_color.name()) + str(self.app.defaults['excellon_plot_fill'][7:9])
-        self.fill_color_entry.set_value(new_val)
-        self.app.defaults['excellon_plot_fill'] = new_val
+    def on_line_color_entry(self):
+        self.app.defaults['excellon_plot_line'] = self.line_color_entry.get_value()[:7] + \
+                                                self.app.defaults['excellon_plot_line'][7:9]
 
-    def on_color_spinner(self):
-        spinner_value = self.color_alpha_spinner.value()
-        self.color_alpha_slider.setValue(spinner_value)
+    def on_excellon_alpha_changed(self, spinner_value):
         self.app.defaults['excellon_plot_fill'] = \
             self.app.defaults['excellon_plot_fill'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
@@ -381,31 +355,6 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
             self.app.defaults['excellon_plot_line'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
 
-    def on_color_slider(self):
-        slider_value = self.color_alpha_slider.value()
-        self.color_alpha_spinner.setValue(slider_value)
-
-    def on_line_color_entry(self):
-        self.app.defaults['excellon_plot_line'] = self.line_color_entry.get_value()[:7] + \
-                                                self.app.defaults['excellon_plot_line'][7:9]
-        self.line_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['excellon_plot_line'])[:7])
-
-    def on_line_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['excellon_plot_line'][:7])
-        # print(current_color)
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_line_color = c_dialog.getColor(initial=current_color)
-
-        if plot_line_color.isValid() is False:
-            return
-
-        self.line_color_button.setStyleSheet("background-color:%s" % str(plot_line_color.name()))
-
-        new_val_line = str(plot_line_color.name()) + str(self.app.defaults['excellon_plot_line'][7:9])
-        self.line_color_entry.set_value(new_val_line)
-        self.app.defaults['excellon_plot_line'] = new_val_line
-
     def on_excellon_defaults_button(self):
         self.app.preferencesUiManager.defaults_form_fields["excellon_format_lower_in"].set_value('4')
         self.app.preferencesUiManager.defaults_form_fields["excellon_format_upper_in"].set_value('2')
@@ -413,3 +362,105 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
         self.app.preferencesUiManager.defaults_form_fields["excellon_format_upper_mm"].set_value('3')
         self.app.preferencesUiManager.defaults_form_fields["excellon_zeros"].set_value('L')
         self.app.preferencesUiManager.defaults_form_fields["excellon_units"].set_value('INCH')
+
+    def on_update_exc_export(self, state):
+        """
+        This is handling the update of Excellon Export parameters based on the ones in the Excellon General but only
+        if the update_excellon_cb checkbox is checked
+
+        :param state: state of the checkbox whose signals is tied to his slot
+        :return:
+        """
+        if state:
+            # first try to disconnect
+            try:
+                self.excellon_format_upper_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
+            except TypeError:
+                pass
+            try:
+                self.excellon_format_lower_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
+            except TypeError:
+                pass
+            try:
+                self.excellon_format_upper_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
+            except TypeError:
+                pass
+            try:
+                self.excellon_format_lower_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
+            except TypeError:
+                pass
+
+            try:
+                self.excellon_zeros_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
+            except TypeError:
+                pass
+            try:
+                self.excellon_units_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
+            except TypeError:
+                pass
+
+            # the connect them
+            self.excellon_format_upper_in_entry.returnPressed.connect(self.on_excellon_format_changed)
+            self.excellon_format_lower_in_entry.returnPressed.connect(self.on_excellon_format_changed)
+            self.excellon_format_upper_mm_entry.returnPressed.connect(self.on_excellon_format_changed)
+            self.excellon_format_lower_mm_entry.returnPressed.connect(self.on_excellon_format_changed)
+            self.excellon_zeros_radio.activated_custom.connect(self.on_excellon_zeros_changed)
+            self.excellon_units_radio.activated_custom.connect(self.on_excellon_units_changed)
+        else:
+            # disconnect the signals
+            try:
+                self.excellon_format_upper_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
+            except TypeError:
+                pass
+            try:
+                self.excellon_format_lower_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
+            except TypeError:
+                pass
+            try:
+                self.excellon_format_upper_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
+            except TypeError:
+                pass
+            try:
+                self.excellon_format_lower_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
+            except TypeError:
+                pass
+
+            try:
+                self.excellon_zeros_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
+            except TypeError:
+                pass
+            try:
+                self.excellon_units_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
+            except TypeError:
+                pass
+
+    def on_excellon_format_changed(self):
+        """
+        Slot activated when the user changes the Excellon format values in Preferences -> Excellon -> Excellon General
+        :return: None
+        """
+        if self.excellon_units_radio.get_value().upper() == 'METRIC':
+            self.app.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
+                self.excellon_format_upper_mm_entry.get_value())
+            self.app.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
+                self.excellon_format_lower_mm_entry.get_value())
+        else:
+            self.app.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
+                self.excellon_format_upper_in_entry.get_value())
+            self.app.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
+                self.excellon_format_lower_in_entry.get_value())
+
+    def on_excellon_zeros_changed(self, val):
+        """
+        Slot activated when the user changes the Excellon zeros values in Preferences -> Excellon -> Excellon General
+        :return: None
+        """
+        self.app.ui.excellon_defaults_form.excellon_exp_group.zeros_radio.set_value(val + 'Z')
+
+    def on_excellon_units_changed(self, val):
+        """
+        Slot activated when the user changes the Excellon unit values in Preferences -> Excellon -> Excellon General
+        :return: None
+        """
+        self.app.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio.set_value(val)
+        self.on_excellon_format_changed()

+ 6 - 6
flatcamGUI/preferences/excellon/ExcellonOptPrefGroupUI.py → AppGUI/preferences/excellon/ExcellonOptPrefGroupUI.py

@@ -1,12 +1,12 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 
-from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
-    FCComboBox
-from flatcamGUI.preferences import machinist_setting
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
+    FCComboBox, NumericalEvalTupleEntry
+from AppGUI.preferences import machinist_setting
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)

+ 7 - 7
flatcamGUI/preferences/excellon/ExcellonPreferencesUI.py → AppGUI/preferences/excellon/ExcellonPreferencesUI.py

@@ -1,14 +1,14 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.preferences.excellon.ExcellonEditorPrefGroupUI import ExcellonEditorPrefGroupUI
-from flatcamGUI.preferences.excellon.ExcellonExpPrefGroupUI import ExcellonExpPrefGroupUI
-from flatcamGUI.preferences.excellon.ExcellonAdvOptPrefGroupUI import ExcellonAdvOptPrefGroupUI
-from flatcamGUI.preferences.excellon.ExcellonOptPrefGroupUI import ExcellonOptPrefGroupUI
-from flatcamGUI.preferences.excellon.ExcellonGenPrefGroupUI import ExcellonGenPrefGroupUI
+from AppGUI.preferences.excellon.ExcellonEditorPrefGroupUI import ExcellonEditorPrefGroupUI
+from AppGUI.preferences.excellon.ExcellonExpPrefGroupUI import ExcellonExpPrefGroupUI
+from AppGUI.preferences.excellon.ExcellonAdvOptPrefGroupUI import ExcellonAdvOptPrefGroupUI
+from AppGUI.preferences.excellon.ExcellonOptPrefGroupUI import ExcellonOptPrefGroupUI
+from AppGUI.preferences.excellon.ExcellonGenPrefGroupUI import ExcellonGenPrefGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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 - 0
flatcamGUI/preferences/excellon/__init__.py → AppGUI/preferences/excellon/__init__.py


+ 32 - 50
flatcamGUI/preferences/general/GeneralAPPSetGroupUI.py → AppGUI/preferences/general/GeneralAPPSetGroupUI.py

@@ -1,13 +1,13 @@
-from PyQt5 import QtCore, QtWidgets, QtGui
+from PyQt5 import QtCore, QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, RadioSet, OptionalInputSection, FCSpinner, \
-    FCEntry
-from flatcamGUI.preferences import settings
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, RadioSet, OptionalInputSection, FCSpinner, \
+    FCColorEntry
+from AppGUI.preferences import settings
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)
 
@@ -201,7 +193,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
         self.notebook_font_size_label = QtWidgets.QLabel('%s:' % _('Notebook'))
         self.notebook_font_size_label.setToolTip(
             _("This sets the font size for the elements found in the Notebook.\n"
-              "The notebook is the collapsible area in the left side of the GUI,\n"
+              "The notebook is the collapsible area in the left side of the AppGUI,\n"
               "and include the Project, Selected and Tool tabs.")
         )
 
@@ -240,8 +232,8 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
         # TextBox Font Size
         self.textbox_font_size_label = QtWidgets.QLabel('%s:' % _('Textbox'))
         self.textbox_font_size_label.setToolTip(
-            _("This sets the font size for the Textbox GUI\n"
-              "elements that are used in FlatCAM.")
+            _("This sets the font size for the Textbox AppGUI\n"
+              "elements that are used in the application.")
         )
 
         self.textbox_font_size_spinner = FCSpinner()
@@ -257,10 +249,29 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
         grid0.addWidget(self.textbox_font_size_label, 13, 0)
         grid0.addWidget(self.textbox_font_size_spinner, 13, 1)
 
+        # HUD Font Size
+        self.hud_font_size_label = QtWidgets.QLabel('%s:' % _('HUD'))
+        self.hud_font_size_label.setToolTip(
+            _("This sets the font size for the Heads Up Display.")
+        )
+
+        self.hud_font_size_spinner = FCSpinner()
+        self.hud_font_size_spinner.set_range(8, 40)
+        self.hud_font_size_spinner.setWrapping(True)
+
+        qsettings = QSettings("Open Source", "FlatCAM")
+        if qsettings.contains("hud_font_size"):
+            self.hud_font_size_spinner.set_value(settings.value('hud_font_size', type=int))
+        else:
+            self.hud_font_size_spinner.set_value(8)
+
+        grid0.addWidget(self.hud_font_size_label, 14, 0)
+        grid0.addWidget(self.hud_font_size_spinner, 14, 1)
+
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 14, 0, 1, 2)
+        grid0.addWidget(separator_line, 16, 0, 1, 2)
 
         # -----------------------------------------------------------
         # -------------- MOUSE SETTINGS -----------------------------
@@ -323,24 +334,16 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
         self.mouse_color_label.setToolTip(
             _("Set the color of the mouse cursor.")
         )
-        self.mouse_cursor_entry = FCEntry()
-        self.mouse_cursor_button = QtWidgets.QPushButton()
-        self.mouse_cursor_button.setFixedSize(15, 15)
-
-        self.form_box_child_1 = QtWidgets.QHBoxLayout()
-        self.form_box_child_1.addWidget(self.mouse_cursor_entry)
-        self.form_box_child_1.addWidget(self.mouse_cursor_button)
-        self.form_box_child_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.mouse_cursor_entry = FCColorEntry()
 
         grid0.addWidget(self.mouse_color_label, 26, 0)
-        grid0.addLayout(self.form_box_child_1, 26, 1)
+        grid0.addWidget(self.mouse_cursor_entry, 26, 1)
 
         self.mois = OptionalInputSection(
             self.mouse_cursor_color_cb,
             [
                 self.mouse_color_label,
-                self.mouse_cursor_entry,
-                self.mouse_cursor_button
+                self.mouse_cursor_entry
             ]
         )
         # Select mouse pan button
@@ -440,9 +443,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
         self.layout.addStretch()
 
         self.mouse_cursor_color_cb.stateChanged.connect(self.on_mouse_cursor_color_enable)
-
         self.mouse_cursor_entry.editingFinished.connect(self.on_mouse_cursor_entry)
-        self.mouse_cursor_button.clicked.connect(self.on_mouse_cursor_button)
 
     def on_mouse_cursor_color_enable(self, val):
         if val:
@@ -461,23 +462,4 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
 
     def on_mouse_cursor_entry(self):
         self.app.defaults['global_cursor_color'] = self.mouse_cursor_entry.get_value()
-        self.mouse_cursor_button.setStyleSheet("background-color:%s" % str(self.app.defaults['global_cursor_color']))
-
-        self.app.cursor_color_3D = self.app.defaults["global_cursor_color"]
-
-    def on_mouse_cursor_button(self):
-        current_color = QtGui.QColor(self.app.defaults['global_cursor_color'])
-
-        c_dialog = QtWidgets.QColorDialog()
-        proj_color = c_dialog.getColor(initial=current_color)
-
-        if proj_color.isValid() is False:
-            return
-
-        self.mouse_cursor_button.setStyleSheet("background-color:%s" % str(proj_color.name()))
-
-        new_val_sel = str(proj_color.name())
-        self.mouse_cursor_entry.set_value(new_val_sel)
-        self.app.defaults['global_cursor_color'] = new_val_sel
-
         self.app.cursor_color_3D = self.app.defaults["global_cursor_color"]

+ 5 - 6
flatcamGUI/preferences/general/GeneralAppPrefGroupUI.py → AppGUI/preferences/general/GeneralAppPrefGroupUI.py

@@ -3,12 +3,12 @@ import sys
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import RadioSet, FCSpinner, FCCheckBox, FCComboBox, FCButton, OptionalInputSection, \
+from AppGUI.GUIElements import RadioSet, FCSpinner, FCCheckBox, FCComboBox, FCButton, OptionalInputSection, \
     FCDoubleSpinner
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -380,12 +380,11 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
 
     def on_toggle_shell_from_settings(self, state):
         """
-        Toggle shell: if is visible close it, if it is closed then open it
+        Toggle shell ui: if is visible close it, if it is closed then open it
+
         :return: None
         """
 
-        self.app.defaults.report_usage("on_toggle_shell_from_settings()")
-
         if state is True:
             if not self.app.ui.shell_dock.isVisible():
                 self.app.ui.shell_dock.show()

+ 302 - 0
AppGUI/preferences/general/GeneralAppSettingsGroupUI.py

@@ -0,0 +1,302 @@
+
+from PyQt5 import QtCore
+from PyQt5.QtCore import QSettings
+from AppGUI.GUIElements import OptionalInputSection
+from AppGUI.preferences import settings
+from AppGUI.preferences.OptionUI import *
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI2
+
+import gettext
+import AppTranslation as fcTranslate
+import builtins
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class GeneralAppSettingsGroupUI(OptionsGroupUI2):
+    def __init__(self, decimals=4, **kwargs):
+        self.decimals = decimals
+        self.pagesize = {}
+        self.pagesize.update(
+            {
+                'A0': (841, 1189),
+                'A1': (594, 841),
+                'A2': (420, 594),
+                'A3': (297, 420),
+                'A4': (210, 297),
+                'A5': (148, 210),
+                'A6': (105, 148),
+                'A7': (74, 105),
+                'A8': (52, 74),
+                'A9': (37, 52),
+                'A10': (26, 37),
+
+                'B0': (1000, 1414),
+                'B1': (707, 1000),
+                'B2': (500, 707),
+                'B3': (353, 500),
+                'B4': (250, 353),
+                'B5': (176, 250),
+                'B6': (125, 176),
+                'B7': (88, 125),
+                'B8': (62, 88),
+                'B9': (44, 62),
+                'B10': (31, 44),
+
+                'C0': (917, 1297),
+                'C1': (648, 917),
+                'C2': (458, 648),
+                'C3': (324, 458),
+                'C4': (229, 324),
+                'C5': (162, 229),
+                'C6': (114, 162),
+                'C7': (81, 114),
+                'C8': (57, 81),
+                'C9': (40, 57),
+                'C10': (28, 40),
+
+                # American paper sizes
+                'LETTER': (8.5, 11),
+                'LEGAL': (8.5, 14),
+                'ELEVENSEVENTEEN': (11, 17),
+
+                # From https://en.wikipedia.org/wiki/Paper_size
+                'JUNIOR_LEGAL': (5, 8),
+                'HALF_LETTER': (5.5, 8),
+                'GOV_LETTER': (8, 10.5),
+                'GOV_LEGAL': (8.5, 13),
+                'LEDGER': (17, 11),
+            }
+        )
+        super().__init__(**kwargs)
+
+        self.setTitle(str(_("App Settings")))
+
+        qsettings = QSettings("Open Source", "FlatCAM")
+
+        self.notebook_font_size_field = self.option_dict()["notebook_font_size"].get_field()
+        if qsettings.contains("notebook_font_size"):
+            self.notebook_font_size_field.set_value(qsettings.value('notebook_font_size', type=int))
+        else:
+            self.notebook_font_size_field.set_value(12)
+
+        self.axis_font_size_field = self.option_dict()["axis_font_size"].get_field()
+        if qsettings.contains("axis_font_size"):
+            self.axis_font_size_field.set_value(qsettings.value('axis_font_size', type=int))
+        else:
+            self.axis_font_size_field.set_value(8)
+
+        self.textbox_font_size_field = self.option_dict()["textbox_font_size"].get_field()
+        if qsettings.contains("textbox_font_size"):
+            self.textbox_font_size_field.set_value(settings.value('textbox_font_size', type=int))
+        else:
+            self.textbox_font_size_field.set_value(10)
+
+        self.workspace_enabled_field = self.option_dict()["global_workspace"].get_field()
+        self.workspace_type_field = self.option_dict()["global_workspaceT"].get_field()
+        self.workspace_type_label = self.option_dict()["global_workspaceT"].label_widget
+        self.workspace_orientation_field = self.option_dict()["global_workspace_orientation"].get_field()
+        self.workspace_orientation_label = self.option_dict()["global_workspace_orientation"].label_widget
+        self.wks = OptionalInputSection(self.workspace_enabled_field, [self.workspace_type_label, self.workspace_type_field, self.workspace_orientation_label, self.workspace_orientation_field])
+
+        self.mouse_cursor_color_enabled_field = self.option_dict()["global_cursor_color_enabled"].get_field()
+        self.mouse_cursor_color_field = self.option_dict()["global_cursor_color"].get_field()
+        self.mouse_cursor_color_label = self.option_dict()["global_cursor_color"].label_widget
+        self.mois = OptionalInputSection(self.mouse_cursor_color_enabled_field, [self.mouse_cursor_color_label, self.mouse_cursor_color_field])
+        self.mouse_cursor_color_enabled_field.stateChanged.connect(self.on_mouse_cursor_color_enable)
+        self.mouse_cursor_color_field.entry.editingFinished.connect(self.on_mouse_cursor_entry)
+
+    def build_options(self) -> [OptionUI]:
+        return [
+            HeadingOptionUI(label_text="Grid Settings", label_tooltip=None),
+            DoubleSpinnerOptionUI(
+                option="global_gridx",
+                label_text="X value",
+                label_tooltip="This is the Grid snap value on X axis.",
+                step=0.1,
+                decimals=self.decimals
+            ),
+            DoubleSpinnerOptionUI(
+                option="global_gridy",
+                label_text='Y value',
+                label_tooltip="This is the Grid snap value on Y axis.",
+                step=0.1,
+                decimals=self.decimals
+            ),
+            DoubleSpinnerOptionUI(
+                option="global_snap_max",
+                label_text="Snap Max",
+                label_tooltip="Max. magnet distance",
+                step=0.1,
+                decimals=self.decimals
+            ),
+            SeparatorOptionUI(),
+
+            HeadingOptionUI(label_text="Workspace Settings", label_tooltip=None),
+            CheckboxOptionUI(
+                option="global_workspace",
+                label_text="Active",
+                label_tooltip="Draw a delimiting rectangle on canvas.\n"
+                              "The purpose is to illustrate the limits for our work."
+            ),
+            ComboboxOptionUI(
+                option="global_workspaceT",
+                label_text="Size",
+                label_tooltip="Select the type of rectangle to be used on canvas,\nas valid workspace.",
+                choices=list(self.pagesize.keys())
+            ),
+            RadioSetOptionUI(
+                option="global_workspace_orientation",
+                label_text="Orientation",
+                label_tooltip="Can be:\n- Portrait\n- Landscape",
+                choices=[
+                    {'label': _('Portrait'), 'value': 'p'},
+                    {'label': _('Landscape'), 'value': 'l'},
+                ]
+            ),
+            # FIXME enabling OptionalInputSection ??
+            SeparatorOptionUI(),
+
+            HeadingOptionUI(label_text="Font Size", label_tooltip=None),
+            SpinnerOptionUI(
+                option="notebook_font_size",
+                label_text="Notebook",
+                label_tooltip="This sets the font size for the elements found in the Notebook.\n"
+                              "The notebook is the collapsible area in the left side of the GUI,\n"
+                              "and include the Project, Selected and Tool tabs.",
+                min_value=8, max_value=40, step=1
+            ),
+            SpinnerOptionUI(
+                option="axis_font_size",
+                label_text="Axis",
+                label_tooltip="This sets the font size for canvas axis.",
+                min_value=8, max_value=40, step=1
+            ),
+            SpinnerOptionUI(
+                option="textbox_font_size",
+                label_text="Textbox",
+                label_tooltip="This sets the font size for the Textbox GUI\n"
+                              "elements that are used in the application.",
+                min_value=8, max_value=40, step=1
+            ),
+            SeparatorOptionUI(),
+
+            HeadingOptionUI(label_text="Mouse Settings", label_tooltip=None),
+            RadioSetOptionUI(
+                option="global_cursor_type",
+                label_text="Cursor Shape",
+                label_tooltip="Choose a mouse cursor shape.\n"
+                              "- Small -> with a customizable size.\n"
+                              "- Big -> Infinite lines",
+                choices=[
+                    {"label": _("Small"), "value": "small"},
+                    {"label": _("Big"), "value": "big"}
+                ]
+            ),
+            SpinnerOptionUI(
+                option="global_cursor_size",
+                label_text="Cursor Size",
+                label_tooltip="Set the size of the mouse cursor, in pixels.",
+                min_value=10, max_value=70, step=1
+            ),
+            SpinnerOptionUI(
+                option="global_cursor_width",
+                label_text="Cursor Width",
+                label_tooltip="Set the line width of the mouse cursor, in pixels.",
+                min_value=1, max_value=10, step=1
+            ),
+            CheckboxOptionUI(
+                option="global_cursor_color_enabled",
+                label_text="Cursor Color",
+                label_tooltip="Check this box to color mouse cursor."
+            ),
+            ColorOptionUI(
+                option="global_cursor_color",
+                label_text="Cursor Color",
+                label_tooltip="Set the color of the mouse cursor."
+            ),
+            # FIXME enabling of cursor color
+            RadioSetOptionUI(
+                option="global_pan_button",
+                label_text="Pan Button",
+                label_tooltip="Select the mouse button to use for panning:\n"
+                              "- MMB --> Middle Mouse Button\n"
+                              "- RMB --> Right Mouse Button",
+                choices=[{'label': _('MMB'), 'value': '3'},
+                         {'label': _('RMB'), 'value': '2'}]
+            ),
+            RadioSetOptionUI(
+                option="global_mselect_key",
+                label_text="Multiple Selection",
+                label_tooltip="Select the key used for multiple selection.",
+                choices=[{'label': _('CTRL'),  'value': 'Control'},
+                         {'label': _('SHIFT'), 'value': 'Shift'}]
+            ),
+            SeparatorOptionUI(),
+
+            CheckboxOptionUI(
+                option="global_delete_confirmation",
+                label_text="Delete object confirmation",
+                label_tooltip="When checked the application will ask for user confirmation\n"
+                              "whenever the Delete object(s) event is triggered, either by\n"
+                              "menu shortcut or key shortcut."
+            ),
+            CheckboxOptionUI(
+                option="global_open_style",
+                label_text='"Open" behavior',
+                label_tooltip="When checked the path for the last saved file is used when saving files,\n"
+                              "and the path for the last opened file is used when opening files.\n\n"
+                              "When unchecked the path for opening files is the one used last: either the\n"
+                              "path for saving files or the path for opening files."
+            ),
+            CheckboxOptionUI(
+                option="global_toggle_tooltips",
+                label_text="Enable ToolTips",
+                label_tooltip="Check this box if you want to have toolTips displayed\n"
+                              "when hovering with mouse over items throughout the App."
+            ),
+            CheckboxOptionUI(
+                option="global_machinist_setting",
+                label_text="Allow Machinist Unsafe Settings",
+                label_tooltip="If checked, some of the application settings will be allowed\n"
+                              "to have values that are usually unsafe to use.\n"
+                              "Like Z travel negative values or Z Cut positive values.\n"
+                              "It will applied at the next application start.\n"
+                              "<<WARNING>>: Don't change this unless you know what you are doing !!!"
+            ),
+            SpinnerOptionUI(
+                option="global_bookmarks_limit",
+                label_text="Bookmarks limit",
+                label_tooltip="The maximum number of bookmarks that may be installed in the menu.\n"
+                              "The number of bookmarks in the bookmark manager may be greater\n"
+                              "but the menu will hold only so much.",
+                min_value=0, max_value=9999, step=1
+            ),
+            ComboboxOptionUI(
+                option="global_activity_icon",
+                label_text="Activity Icon",
+                label_tooltip="Select the GIF that show activity when FlatCAM is active.",
+                choices=['Ball black', 'Ball green', 'Arrow green', 'Eclipse green']
+            )
+
+        ]
+
+    def on_mouse_cursor_color_enable(self, val):
+        if val:
+            self.app.cursor_color_3D = self.app.defaults["global_cursor_color"]
+        else:
+            theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
+            if theme_settings.contains("theme"):
+                theme = theme_settings.value('theme', type=str)
+            else:
+                theme = 'white'
+
+            if theme == 'white':
+                self.app.cursor_color_3D = 'black'
+            else:
+                self.app.cursor_color_3D = 'gray'
+
+    def on_mouse_cursor_entry(self):
+        self.app.defaults['global_cursor_color'] = self.mouse_cursor_color_field.get_value()
+        self.app.cursor_color_3D = self.app.defaults["global_cursor_color"]

+ 73 - 303
flatcamGUI/preferences/general/GeneralGUIPrefGroupUI.py → AppGUI/preferences/general/GeneralGUIPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import QSettings, Qt
 
-from flatcamGUI.GUIElements import RadioSet, FCCheckBox, FCButton, FCComboBox, FCEntry, FCSpinner
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import RadioSet, FCCheckBox, FCComboBox, FCSliderWithSpinner, FCColorEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -35,7 +35,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         # Theme selection
         self.theme_label = QtWidgets.QLabel('%s:' % _('Theme'))
         self.theme_label.setToolTip(
-            _("Select a theme for FlatCAM.\n"
+            _("Select a theme for the application.\n"
               "It will theme the plot area.")
         )
 
@@ -72,7 +72,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         # Layout selection
         self.layout_label = QtWidgets.QLabel('%s:' % _('Layout'))
         self.layout_label.setToolTip(
-            _("Select an layout for FlatCAM.\n"
+            _("Select a layout for the application.\n"
               "It is applied immediately.")
         )
         self.layout_combo = FCComboBox()
@@ -94,7 +94,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         # Style selection
         self.style_label = QtWidgets.QLabel('%s:' % _('Style'))
         self.style_label.setToolTip(
-            _("Select an style for FlatCAM.\n"
+            _("Select a style for the application.\n"
               "It will be applied at the next app start.")
         )
         self.style_combo = FCComboBox()
@@ -110,7 +110,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         # Enable High DPI Support
         self.hdpi_cb = FCCheckBox('%s' % _('Activate HDPI Support'))
         self.hdpi_cb.setToolTip(
-            _("Enable High DPI support for FlatCAM.\n"
+            _("Enable High DPI support for the application.\n"
               "It will be applied at the next app start.")
         )
 
@@ -126,7 +126,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         # Enable Hover box
         self.hover_cb = FCCheckBox('%s' % _('Display Hover Shape'))
         self.hover_cb.setToolTip(
-            _("Enable display of a hover shape for FlatCAM objects.\n"
+            _("Enable display of a hover shape for the application objects.\n"
               "It is displayed whenever the mouse cursor is hovering\n"
               "over any kind of not-selected object.")
         )
@@ -135,7 +135,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         # Enable Selection box
         self.selection_cb = FCCheckBox('%s' % _('Display Selection Shape'))
         self.selection_cb.setToolTip(
-            _("Enable the display of a selection shape for FlatCAM objects.\n"
+            _("Enable the display of a selection shape for the application objects.\n"
               "It is displayed whenever the mouse selects an object\n"
               "either by clicking or dragging mouse from left to right or\n"
               "right to left.")
@@ -155,17 +155,10 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.sl_color_label.setToolTip(
             _("Set the line color for the 'left to right' selection box.")
         )
-        self.sl_color_entry = FCEntry()
-        self.sl_color_button = QtWidgets.QPushButton()
-        self.sl_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_4 = QtWidgets.QHBoxLayout()
-        self.form_box_child_4.addWidget(self.sl_color_entry)
-        self.form_box_child_4.addWidget(self.sl_color_button)
-        self.form_box_child_4.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.sl_color_entry = FCColorEntry()
 
         grid0.addWidget(self.sl_color_label, 16, 0)
-        grid0.addLayout(self.form_box_child_4, 16, 1)
+        grid0.addWidget(self.sl_color_entry, 16, 1)
 
         self.sf_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
         self.sf_color_label.setToolTip(
@@ -174,38 +167,20 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.sf_color_entry = FCEntry()
-        self.sf_color_button = QtWidgets.QPushButton()
-        self.sf_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_5 = QtWidgets.QHBoxLayout()
-        self.form_box_child_5.addWidget(self.sf_color_entry)
-        self.form_box_child_5.addWidget(self.sf_color_button)
-        self.form_box_child_5.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.sf_color_entry = FCColorEntry()
 
         grid0.addWidget(self.sf_color_label, 17, 0)
-        grid0.addLayout(self.form_box_child_5, 17, 1)
+        grid0.addWidget(self.sf_color_entry, 17, 1)
 
         # Plot Selection (left - right) Fill Transparency Level
-        self.sf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
-        self.sf_alpha_label.setToolTip(
+        self.left_right_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
+        self.left_right_alpha_label.setToolTip(
             _("Set the fill transparency for the 'left to right' selection box.")
         )
-        self.sf_color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
-        self.sf_color_alpha_slider.setMinimum(0)
-        self.sf_color_alpha_slider.setMaximum(255)
-        self.sf_color_alpha_slider.setSingleStep(1)
-
-        self.sf_color_alpha_spinner = FCSpinner()
-        self.sf_color_alpha_spinner.setMinimumWidth(70)
-        self.sf_color_alpha_spinner.set_range(0, 255)
+        self.left_right_alpha_entry = FCSliderWithSpinner(0, 255, 1)
 
-        self.form_box_child_6 = QtWidgets.QHBoxLayout()
-        self.form_box_child_6.addWidget(self.sf_color_alpha_slider)
-        self.form_box_child_6.addWidget(self.sf_color_alpha_spinner)
-
-        grid0.addWidget(self.sf_alpha_label, 18, 0)
-        grid0.addLayout(self.form_box_child_6, 18, 1)
+        grid0.addWidget(self.left_right_alpha_label, 18, 0)
+        grid0.addWidget(self.left_right_alpha_entry, 18, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -221,17 +196,10 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.alt_sl_color_label.setToolTip(
             _("Set the line color for the 'right to left' selection box.")
         )
-        self.alt_sl_color_entry = FCEntry()
-        self.alt_sl_color_button = QtWidgets.QPushButton()
-        self.alt_sl_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_7 = QtWidgets.QHBoxLayout()
-        self.form_box_child_7.addWidget(self.alt_sl_color_entry)
-        self.form_box_child_7.addWidget(self.alt_sl_color_button)
-        self.form_box_child_7.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.alt_sl_color_entry = FCColorEntry()
 
         grid0.addWidget(self.alt_sl_color_label, 21, 0)
-        grid0.addLayout(self.form_box_child_7, 21, 1)
+        grid0.addWidget(self.alt_sl_color_entry, 21, 1)
 
         # Plot Selection (right - left) Fill Color
         self.alt_sf_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
@@ -241,38 +209,20 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.alt_sf_color_entry = FCEntry()
-        self.alt_sf_color_button = QtWidgets.QPushButton()
-        self.alt_sf_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_8 = QtWidgets.QHBoxLayout()
-        self.form_box_child_8.addWidget(self.alt_sf_color_entry)
-        self.form_box_child_8.addWidget(self.alt_sf_color_button)
-        self.form_box_child_8.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.alt_sf_color_entry = FCColorEntry()
 
         grid0.addWidget(self.alt_sf_color_label, 22, 0)
-        grid0.addLayout(self.form_box_child_8, 22, 1)
+        grid0.addWidget(self.alt_sf_color_entry, 22, 1)
 
         # Plot Selection (right - left) Fill Transparency Level
-        self.alt_sf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
-        self.alt_sf_alpha_label.setToolTip(
+        self.right_left_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
+        self.right_left_alpha_label.setToolTip(
             _("Set the fill transparency for selection 'right to left' box.")
         )
-        self.alt_sf_color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
-        self.alt_sf_color_alpha_slider.setMinimum(0)
-        self.alt_sf_color_alpha_slider.setMaximum(255)
-        self.alt_sf_color_alpha_slider.setSingleStep(1)
-
-        self.alt_sf_color_alpha_spinner = FCSpinner()
-        self.alt_sf_color_alpha_spinner.setMinimumWidth(70)
-        self.alt_sf_color_alpha_spinner.set_range(0, 255)
-
-        self.form_box_child_9 = QtWidgets.QHBoxLayout()
-        self.form_box_child_9.addWidget(self.alt_sf_color_alpha_slider)
-        self.form_box_child_9.addWidget(self.alt_sf_color_alpha_spinner)
+        self.right_left_alpha_entry = FCSliderWithSpinner(0, 255, 1)
 
-        grid0.addWidget(self.alt_sf_alpha_label, 23, 0)
-        grid0.addLayout(self.form_box_child_9, 23, 1)
+        grid0.addWidget(self.right_left_alpha_label, 23, 0)
+        grid0.addWidget(self.right_left_alpha_entry, 23, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -291,34 +241,20 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.alt_sf_color_label.setToolTip(
             _("Set the color for the shape.")
         )
-        self.draw_color_entry = FCEntry()
-        self.draw_color_button = QtWidgets.QPushButton()
-        self.draw_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_10 = QtWidgets.QHBoxLayout()
-        self.form_box_child_10.addWidget(self.draw_color_entry)
-        self.form_box_child_10.addWidget(self.draw_color_button)
-        self.form_box_child_10.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.draw_color_entry = FCColorEntry()
 
         grid0.addWidget(self.draw_color_label, 26, 0)
-        grid0.addLayout(self.form_box_child_10, 26, 1)
+        grid0.addWidget(self.draw_color_entry, 26, 1)
 
         # Editor Draw Selection Color
         self.sel_draw_color_label = QtWidgets.QLabel('%s:' % _('Selection'))
         self.sel_draw_color_label.setToolTip(
             _("Set the color of the shape when selected.")
         )
-        self.sel_draw_color_entry = FCEntry()
-        self.sel_draw_color_button = QtWidgets.QPushButton()
-        self.sel_draw_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_11 = QtWidgets.QHBoxLayout()
-        self.form_box_child_11.addWidget(self.sel_draw_color_entry)
-        self.form_box_child_11.addWidget(self.sel_draw_color_button)
-        self.form_box_child_11.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.sel_draw_color_entry = FCColorEntry()
 
         grid0.addWidget(self.sel_draw_color_label, 27, 0)
-        grid0.addLayout(self.form_box_child_11, 27, 1)
+        grid0.addWidget(self.sel_draw_color_entry, 27, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -337,34 +273,20 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.proj_color_label.setToolTip(
             _("Set the color of the items in Project Tab Tree.")
         )
-        self.proj_color_entry = FCEntry()
-        self.proj_color_button = QtWidgets.QPushButton()
-        self.proj_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_12 = QtWidgets.QHBoxLayout()
-        self.form_box_child_12.addWidget(self.proj_color_entry)
-        self.form_box_child_12.addWidget(self.proj_color_button)
-        self.form_box_child_12.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.proj_color_entry = FCColorEntry()
 
         grid0.addWidget(self.proj_color_label, 30, 0)
-        grid0.addLayout(self.form_box_child_12, 30, 1)
+        grid0.addWidget(self.proj_color_entry, 30, 1)
 
         self.proj_color_dis_label = QtWidgets.QLabel('%s:' % _('Disabled'))
         self.proj_color_dis_label.setToolTip(
             _("Set the color of the items in Project Tab Tree,\n"
               "for the case when the items are disabled.")
         )
-        self.proj_color_dis_entry = FCEntry()
-        self.proj_color_dis_button = QtWidgets.QPushButton()
-        self.proj_color_dis_button.setFixedSize(15, 15)
-
-        self.form_box_child_13 = QtWidgets.QHBoxLayout()
-        self.form_box_child_13.addWidget(self.proj_color_dis_entry)
-        self.form_box_child_13.addWidget(self.proj_color_dis_button)
-        self.form_box_child_13.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.proj_color_dis_entry = FCColorEntry()
 
         grid0.addWidget(self.proj_color_dis_label, 31, 0)
-        grid0.addLayout(self.form_box_child_13, 31, 1)
+        grid0.addWidget(self.proj_color_dis_entry, 31, 1)
 
         # Project autohide CB
         self.project_autohide_cb = FCCheckBox(label=_('Project AutoHide'))
@@ -387,32 +309,22 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
 
         # Setting selection (left - right) colors signals
         self.sf_color_entry.editingFinished.connect(self.on_sf_color_entry)
-        self.sf_color_button.clicked.connect(self.on_sf_color_button)
-        self.sf_color_alpha_spinner.valueChanged.connect(self.on_sf_color_spinner)
-        self.sf_color_alpha_slider.valueChanged.connect(self.on_sf_color_slider)
         self.sl_color_entry.editingFinished.connect(self.on_sl_color_entry)
-        self.sl_color_button.clicked.connect(self.on_sl_color_button)
+
+        self.left_right_alpha_entry.valueChanged.connect(self.on_left_right_alpha_changed)  # alpha
 
         # Setting selection (right - left) colors signals
         self.alt_sf_color_entry.editingFinished.connect(self.on_alt_sf_color_entry)
-        self.alt_sf_color_button.clicked.connect(self.on_alt_sf_color_button)
-        self.alt_sf_color_alpha_spinner.valueChanged.connect(self.on_alt_sf_color_spinner)
-        self.alt_sf_color_alpha_slider.valueChanged.connect(self.on_alt_sf_color_slider)
         self.alt_sl_color_entry.editingFinished.connect(self.on_alt_sl_color_entry)
-        self.alt_sl_color_button.clicked.connect(self.on_alt_sl_color_button)
+
+        self.right_left_alpha_entry.valueChanged.connect(self.on_right_left_alpha_changed)  # alpha
 
         # Setting Editor Draw colors signals
         self.draw_color_entry.editingFinished.connect(self.on_draw_color_entry)
-        self.draw_color_button.clicked.connect(self.on_draw_color_button)
-
         self.sel_draw_color_entry.editingFinished.connect(self.on_sel_draw_color_entry)
-        self.sel_draw_color_button.clicked.connect(self.on_sel_draw_color_button)
 
         self.proj_color_entry.editingFinished.connect(self.on_proj_color_entry)
-        self.proj_color_button.clicked.connect(self.on_proj_color_button)
-
         self.proj_color_dis_entry.editingFinished.connect(self.on_proj_color_dis_entry)
-        self.proj_color_dis_button.clicked.connect(self.on_proj_color_dis_button)
 
         self.layout_combo.activated.connect(self.on_layout)
 
@@ -437,191 +349,64 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
     # Setting selection colors (left - right) handlers
     def on_sf_color_entry(self):
         self.app.defaults['global_sel_fill'] = self.app.defaults['global_sel_fill'][7:9]
-        self.sf_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['global_sel_fill'])[:7])
-
-    def on_sf_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['global_sel_fill'][:7])
 
-        c_dialog = QtWidgets.QColorDialog()
-        plot_fill_color = c_dialog.getColor(initial=current_color)
-
-        if plot_fill_color.isValid() is False:
-            return
+    def on_sl_color_entry(self):
+        self.app.defaults['global_sel_line'] = self.sl_color_entry.get_value()[:7] + \
+            self.app.defaults['global_sel_line'][7:9]
 
-        self.sf_color_button.setStyleSheet("background-color:%s" % str(plot_fill_color.name()))
+    def on_left_right_alpha_changed(self, spinner_value):
+        """
+        Change the alpha level for the color of the selection box when selection is done left to right.
+        Called on valueChanged of a FCSliderWithSpinner.
 
-        new_val = str(plot_fill_color.name()) + str(self.app.defaults['global_sel_fill'][7:9])
-        self.sf_color_entry.set_value(new_val)
-        self.app.defaults['global_sel_fill'] = new_val
+        :param spinner_value:   passed value within [0, 255]
+        :type spinner_value:    int
+        :return:                None
+        :rtype:
+        """
 
-    def on_sf_color_spinner(self):
-        spinner_value = self.sf_color_alpha_spinner.value()
-        self.sf_color_alpha_slider.setValue(spinner_value)
         self.app.defaults['global_sel_fill'] = self.app.defaults['global_sel_fill'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
         self.app.defaults['global_sel_line'] = self.app.defaults['global_sel_line'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
 
-    def on_sf_color_slider(self):
-        slider_value = self.sf_color_alpha_slider.value()
-        self.sf_color_alpha_spinner.setValue(slider_value)
-
-    def on_sl_color_entry(self):
-        self.app.defaults['global_sel_line'] = self.sl_color_entry.get_value()[:7] + \
-            self.app.defaults['global_sel_line'][7:9]
-        self.sl_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['global_sel_line'])[:7])
-
-    def on_sl_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['global_sel_line'][:7])
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_line_color = c_dialog.getColor(initial=current_color)
-
-        if plot_line_color.isValid() is False:
-            return
-
-        self.sl_color_button.setStyleSheet("background-color:%s" % str(plot_line_color.name()))
-
-        new_val_line = str(plot_line_color.name()) + str(self.app.defaults['global_sel_line'][7:9])
-        self.sl_color_entry.set_value(new_val_line)
-        self.app.defaults['global_sel_line'] = new_val_line
-
     # Setting selection colors (right - left) handlers
     def on_alt_sf_color_entry(self):
         self.app.defaults['global_alt_sel_fill'] = self.alt_sf_color_entry.get_value()[:7] + \
                                                    self.app.defaults['global_alt_sel_fill'][7:9]
-        self.alt_sf_color_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['global_alt_sel_fill'])[:7]
-        )
 
-    def on_alt_sf_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['global_alt_sel_fill'][:7])
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_fill_color = c_dialog.getColor(initial=current_color)
-
-        if plot_fill_color.isValid() is False:
-            return
+    def on_alt_sl_color_entry(self):
+        self.app.defaults['global_alt_sel_line'] = self.alt_sl_color_entry.get_value()[:7] + \
+                                                   self.app.defaults['global_alt_sel_line'][7:9]
 
-        self.alt_sf_color_button.setStyleSheet("background-color:%s" % str(plot_fill_color.name()))
+    def on_right_left_alpha_changed(self, spinner_value):
+        """
+        Change the alpha level for the color of the selection box when selection is done right to left.
+        Called on valueChanged of a FCSliderWithSpinner.
 
-        new_val = str(plot_fill_color.name()) + str(self.app.defaults['global_alt_sel_fill'][7:9])
-        self.alt_sf_color_entry.set_value(new_val)
-        self.app.defaults['global_alt_sel_fill'] = new_val
+        :param spinner_value:   passed value within [0, 255]
+        :type spinner_value:    int
+        :return:                None
+        :rtype:
+        """
 
-    def on_alt_sf_color_spinner(self):
-        spinner_value = self.alt_sf_color_alpha_spinner.value()
-        self.alt_sf_color_alpha_slider.setValue(spinner_value)
         self.app.defaults['global_alt_sel_fill'] = self.app.defaults['global_alt_sel_fill'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
         self.app.defaults['global_alt_sel_line'] = self.app.defaults['global_alt_sel_line'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
 
-    def on_alt_sf_color_slider(self):
-        slider_value = self.alt_sf_color_alpha_slider.value()
-        self.alt_sf_color_alpha_spinner.setValue(slider_value)
-
-    def on_alt_sl_color_entry(self):
-        self.app.defaults['global_alt_sel_line'] = self.alt_sl_color_entry.get_value()[:7] + \
-                                                   self.app.defaults['global_alt_sel_line'][7:9]
-        self.alt_sl_color_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['global_alt_sel_line'])[:7]
-        )
-
-    def on_alt_sl_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['global_alt_sel_line'][:7])
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_line_color = c_dialog.getColor(initial=current_color)
-
-        if plot_line_color.isValid() is False:
-            return
-
-        self.alt_sl_color_button.setStyleSheet("background-color:%s" % str(plot_line_color.name()))
-
-        new_val_line = str(plot_line_color.name()) + str(self.app.defaults['global_alt_sel_line'][7:9])
-        self.alt_sl_color_entry.set_value(new_val_line)
-        self.app.defaults['global_alt_sel_line'] = new_val_line
-
     # Setting Editor colors
     def on_draw_color_entry(self):
         self.app.defaults['global_draw_color'] = self.draw_color_entry.get_value()
-        self.draw_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['global_draw_color']))
-
-    def on_draw_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['global_draw_color'])
-
-        c_dialog = QtWidgets.QColorDialog()
-        draw_color = c_dialog.getColor(initial=current_color)
-
-        if draw_color.isValid() is False:
-            return
-
-        self.draw_color_button.setStyleSheet("background-color:%s" % str(draw_color.name()))
-
-        new_val = str(draw_color.name())
-        self.draw_color_entry.set_value(new_val)
-        self.app.defaults['global_draw_color'] = new_val
 
     def on_sel_draw_color_entry(self):
         self.app.defaults['global_sel_draw_color'] = self.sel_draw_color_entry.get_value()
-        self.sel_draw_color_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['global_sel_draw_color']))
-
-    def on_sel_draw_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['global_sel_draw_color'])
-
-        c_dialog = QtWidgets.QColorDialog()
-        sel_draw_color = c_dialog.getColor(initial=current_color)
-
-        if sel_draw_color.isValid() is False:
-            return
-
-        self.sel_draw_color_button.setStyleSheet("background-color:%s" % str(sel_draw_color.name()))
-
-        new_val_sel = str(sel_draw_color.name())
-        self.sel_draw_color_entry.set_value(new_val_sel)
-        self.app.defaults['global_sel_draw_color'] = new_val_sel
 
     def on_proj_color_entry(self):
         self.app.defaults['global_proj_item_color'] = self.proj_color_entry.get_value()
-        self.proj_color_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['global_proj_item_color']))
-
-    def on_proj_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['global_proj_item_color'])
-
-        c_dialog = QtWidgets.QColorDialog()
-        proj_color = c_dialog.getColor(initial=current_color)
-
-        if proj_color.isValid() is False:
-            return
-
-        self.proj_color_button.setStyleSheet("background-color:%s" % str(proj_color.name()))
-
-        new_val_sel = str(proj_color.name())
-        self.proj_color_entry.set_value(new_val_sel)
-        self.app.defaults['global_proj_item_color'] = new_val_sel
 
     def on_proj_color_dis_entry(self):
         self.app.defaults['global_proj_item_dis_color'] = self.proj_color_dis_entry.get_value()
-        self.proj_color_dis_button.setStyleSheet(
-            "background-color:%s" % str(self.app.defaults['global_proj_item_dis_color']))
-
-    def on_proj_color_dis_button(self):
-        current_color = QtGui.QColor(self.app.defaults['global_proj_item_dis_color'])
-
-        c_dialog = QtWidgets.QColorDialog()
-        proj_color = c_dialog.getColor(initial=current_color)
-
-        if proj_color.isValid() is False:
-            return
-
-        self.proj_color_dis_button.setStyleSheet("background-color:%s" % str(proj_color.name()))
-
-        new_val_sel = str(proj_color.name())
-        self.proj_color_dis_entry.set_value(new_val_sel)
-        self.app.defaults['global_proj_item_dis_color'] = new_val_sel
 
     def on_layout(self, index=None, lay=None):
         """
@@ -647,14 +432,13 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         # first remove the toolbars:
         try:
             self.app.ui.removeToolBar(self.app.ui.toolbarfile)
-            self.app.ui.removeToolBar(self.app.ui.toolbargeo)
+            self.app.ui.removeToolBar(self.app.ui.toolbaredit)
             self.app.ui.removeToolBar(self.app.ui.toolbarview)
             self.app.ui.removeToolBar(self.app.ui.toolbarshell)
             self.app.ui.removeToolBar(self.app.ui.toolbartools)
             self.app.ui.removeToolBar(self.app.ui.exc_edit_toolbar)
             self.app.ui.removeToolBar(self.app.ui.geo_edit_toolbar)
             self.app.ui.removeToolBar(self.app.ui.grb_edit_toolbar)
-            self.app.ui.removeToolBar(self.app.ui.snap_toolbar)
             self.app.ui.removeToolBar(self.app.ui.toolbarshell)
         except Exception:
             pass
@@ -665,9 +449,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
             self.app.ui.toolbarfile.setObjectName('File_TB')
             self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbarfile)
 
-            self.app.ui.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
-            self.app.ui.toolbargeo.setObjectName('Edit_TB')
-            self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbargeo)
+            self.app.ui.toolbaredit = QtWidgets.QToolBar('Edit Toolbar')
+            self.app.ui.toolbaredit.setObjectName('Edit_TB')
+            self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbaredit)
 
             self.app.ui.toolbarshell = QtWidgets.QToolBar('Shell Toolbar')
             self.app.ui.toolbarshell.setObjectName('Shell_TB')
@@ -697,22 +481,15 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
             self.app.ui.exc_edit_toolbar.setObjectName('ExcEditor_TB')
             self.app.ui.addToolBar(Qt.RightToolBarArea, self.app.ui.exc_edit_toolbar)
 
-            self.app.ui.snap_toolbar = QtWidgets.QToolBar('Grid Toolbar')
-            self.app.ui.snap_toolbar.setObjectName('Snap_TB')
-            self.app.ui.snap_toolbar.setMaximumHeight(30)
-            self.app.ui.splitter_left.addWidget(self.app.ui.snap_toolbar)
-
-            self.app.ui.corner_snap_btn.setVisible(True)
-            self.app.ui.snap_magnet.setVisible(True)
         else:
             # ## TOOLBAR INSTALLATION # ##
             self.app.ui.toolbarfile = QtWidgets.QToolBar('File Toolbar')
             self.app.ui.toolbarfile.setObjectName('File_TB')
             self.app.ui.addToolBar(self.app.ui.toolbarfile)
 
-            self.app.ui.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
-            self.app.ui.toolbargeo.setObjectName('Edit_TB')
-            self.app.ui.addToolBar(self.app.ui.toolbargeo)
+            self.app.ui.toolbaredit = QtWidgets.QToolBar('Edit Toolbar')
+            self.app.ui.toolbaredit.setObjectName('Edit_TB')
+            self.app.ui.addToolBar(self.app.ui.toolbaredit)
 
             self.app.ui.toolbarview = QtWidgets.QToolBar('View Toolbar')
             self.app.ui.toolbarview.setObjectName('View_TB')
@@ -743,18 +520,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
             self.app.ui.grb_edit_toolbar.setObjectName('GrbEditor_TB')
             self.app.ui.addToolBar(self.app.ui.grb_edit_toolbar)
 
-            self.app.ui.snap_toolbar = QtWidgets.QToolBar('Grid Toolbar')
-            self.app.ui.snap_toolbar.setObjectName('Snap_TB')
-            # self.app.ui.snap_toolbar.setMaximumHeight(30)
-            self.app.ui.addToolBar(self.app.ui.snap_toolbar)
-
-            self.app.ui.corner_snap_btn.setVisible(False)
-            self.app.ui.snap_magnet.setVisible(False)
-
         if current_layout == 'minimal':
             self.app.ui.toolbarview.setVisible(False)
             self.app.ui.toolbarshell.setVisible(False)
-            self.app.ui.snap_toolbar.setVisible(False)
             self.app.ui.geo_edit_toolbar.setVisible(False)
             self.app.ui.grb_edit_toolbar.setVisible(False)
             self.app.ui.exc_edit_toolbar.setVisible(False)
@@ -767,7 +535,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.app.connect_toolbar_signals()
 
         self.app.ui.grid_snap_btn.setChecked(True)
-        self.app.ui.on_grid_snap_triggered(state=True)
+
+        self.app.ui.corner_snap_btn.setVisible(False)
+        self.app.ui.snap_magnet.setVisible(False)
 
         self.app.ui.grid_gap_x_entry.setText(str(self.app.defaults["global_gridx"]))
         self.app.ui.grid_gap_y_entry.setText(str(self.app.defaults["global_gridy"]))

+ 4 - 4
flatcamGUI/preferences/general/GeneralPreferencesUI.py → AppGUI/preferences/general/GeneralPreferencesUI.py

@@ -1,12 +1,12 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.preferences.general.GeneralAppPrefGroupUI import GeneralAppPrefGroupUI
-from flatcamGUI.preferences.general.GeneralAPPSetGroupUI import GeneralAPPSetGroupUI
-from flatcamGUI.preferences.general.GeneralGUIPrefGroupUI import GeneralGUIPrefGroupUI
+from AppGUI.preferences.general.GeneralAppPrefGroupUI import GeneralAppPrefGroupUI
+from AppGUI.preferences.general.GeneralAPPSetGroupUI import GeneralAPPSetGroupUI
+from AppGUI.preferences.general.GeneralGUIPrefGroupUI import GeneralGUIPrefGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 0 - 0
flatcamGUI/preferences/general/__init__.py → AppGUI/preferences/general/__init__.py


+ 20 - 12
flatcamGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py → AppGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py

@@ -1,11 +1,12 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCEntry, FloatEntry, FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel, NumericalEvalTupleEntry, \
+    NumericalEvalEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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"))

+ 3 - 3
flatcamGUI/preferences/geometry/GeometryEditorPrefGroupUI.py → AppGUI/preferences/geometry/GeometryEditorPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCSpinner, RadioSet
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCSpinner, RadioSet
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 18 - 32
flatcamGUI/preferences/geometry/GeometryGenPrefGroupUI.py → AppGUI/preferences/geometry/GeometryGenPrefGroupUI.py

@@ -1,11 +1,11 @@
-from PyQt5 import QtWidgets, QtCore, QtGui
+from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCCheckBox, FCSpinner, FCEntry, FCColorEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)
@@ -77,7 +87,7 @@ class GeometryGenPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(separator_line, 9, 0, 1, 2)
 
         # Geometry Object Color
-        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Geometry Object Color'))
+        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Object Color'))
         grid0.addWidget(self.gerber_color_label, 10, 0, 1, 2)
 
         # Plot Line Color
@@ -85,39 +95,15 @@ class GeometryGenPrefGroupUI(OptionsGroupUI):
         self.line_color_label.setToolTip(
             _("Set the line color for plotted objects.")
         )
-        self.line_color_entry = FCEntry()
-        self.line_color_button = QtWidgets.QPushButton()
-        self.line_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_2 = QtWidgets.QHBoxLayout()
-        self.form_box_child_2.addWidget(self.line_color_entry)
-        self.form_box_child_2.addWidget(self.line_color_button)
-        self.form_box_child_2.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.line_color_entry = FCColorEntry()
 
         grid0.addWidget(self.line_color_label, 11, 0)
-        grid0.addLayout(self.form_box_child_2, 11, 1)
+        grid0.addWidget(self.line_color_entry, 11, 1)
 
         self.layout.addStretch()
 
         # Setting plot colors signals
         self.line_color_entry.editingFinished.connect(self.on_line_color_entry)
-        self.line_color_button.clicked.connect(self.on_line_color_button)
 
     def on_line_color_entry(self):
         self.app.defaults['geometry_plot_line'] = self.line_color_entry.get_value()[:7] + 'FF'
-        self.line_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['geometry_plot_line'])[:7])
-
-    def on_line_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['geometry_plot_line'][:7])
-        # print(current_color)
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_line_color = c_dialog.getColor(initial=current_color)
-
-        if plot_line_color.isValid() is False:
-            return
-
-        self.line_color_button.setStyleSheet("background-color:%s" % str(plot_line_color.name()))
-
-        new_val_line = str(plot_line_color.name()) + str(self.app.defaults['geometry_plot_line'][7:9])
-        self.line_color_entry.set_value(new_val_line)

+ 6 - 5
flatcamGUI/preferences/geometry/GeometryOptPrefGroupUI.py → AppGUI/preferences/geometry/GeometryOptPrefGroupUI.py

@@ -1,12 +1,13 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCEntry, FCSpinner, FCComboBox
-from flatcamGUI.preferences import machinist_setting
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCSpinner, FCComboBox, \
+    NumericalEvalTupleEntry
+from AppGUI.preferences import machinist_setting
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)

+ 5 - 5
flatcamGUI/preferences/geometry/GeometryPreferencesUI.py → AppGUI/preferences/geometry/GeometryPreferencesUI.py

@@ -1,13 +1,13 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.preferences.geometry.GeometryEditorPrefGroupUI import GeometryEditorPrefGroupUI
-from flatcamGUI.preferences.geometry.GeometryAdvOptPrefGroupUI import GeometryAdvOptPrefGroupUI
-from flatcamGUI.preferences.geometry.GeometryOptPrefGroupUI import GeometryOptPrefGroupUI
-from flatcamGUI.preferences.geometry.GeometryGenPrefGroupUI import GeometryGenPrefGroupUI
+from AppGUI.preferences.geometry.GeometryEditorPrefGroupUI import GeometryEditorPrefGroupUI
+from AppGUI.preferences.geometry.GeometryAdvOptPrefGroupUI import GeometryAdvOptPrefGroupUI
+from AppGUI.preferences.geometry.GeometryOptPrefGroupUI import GeometryOptPrefGroupUI
+from AppGUI.preferences.geometry.GeometryGenPrefGroupUI import GeometryGenPrefGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 0 - 0
flatcamGUI/preferences/geometry/__init__.py → AppGUI/preferences/geometry/__init__.py


+ 3 - 82
flatcamGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py → AppGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner, FCSpinner, OptionalInputSection
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner, FCSpinner, OptionalInputSection
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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(

+ 5 - 4
flatcamGUI/preferences/gerber/GerberEditorPrefGroupUI.py → AppGUI/preferences/gerber/GerberEditorPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet, NumericalEvalTupleEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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'))

+ 3 - 3
flatcamGUI/preferences/gerber/GerberExpPrefGroupUI.py → AppGUI/preferences/gerber/GerberExpPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets, QtCore
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import RadioSet, FCSpinner
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import RadioSet, FCSpinner
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 38 - 104
flatcamGUI/preferences/gerber/GerberGenPrefGroupUI.py → AppGUI/preferences/gerber/GerberGenPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry, FCSliderWithSpinner, FCColorEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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"))
@@ -146,100 +146,55 @@ class GerberGenPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(separator_line, 9, 0, 1, 3)
 
         # Gerber Object Color
-        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Gerber Object Color'))
+        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Object Color'))
         grid0.addWidget(self.gerber_color_label, 10, 0, 1, 3)
 
         # Plot Line Color
-        self.pl_color_label = QtWidgets.QLabel('%s:' % _('Outline'))
-        self.pl_color_label.setToolTip(
+        self.line_color_label = QtWidgets.QLabel('%s:' % _('Outline'))
+        self.line_color_label.setToolTip(
             _("Set the line color for plotted objects.")
         )
-        self.pl_color_entry = FCEntry()
-        self.pl_color_button = QtWidgets.QPushButton()
-        self.pl_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_2 = QtWidgets.QHBoxLayout()
-        self.form_box_child_2.addWidget(self.pl_color_entry)
-        self.form_box_child_2.addWidget(self.pl_color_button)
-        self.form_box_child_2.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.line_color_entry = FCColorEntry()
 
-        grid0.addWidget(self.pl_color_label, 11, 0)
-        grid0.addLayout(self.form_box_child_2, 11, 1, 1, 2)
+        grid0.addWidget(self.line_color_label, 11, 0)
+        grid0.addWidget(self.line_color_entry, 11, 1, 1, 2)
 
         # Plot Fill Color
-        self.pf_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
-        self.pf_color_label.setToolTip(
+        self.fill_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
+        self.fill_color_label.setToolTip(
             _("Set the fill color for plotted objects.\n"
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.pf_color_entry = FCEntry()
-        self.pf_color_button = QtWidgets.QPushButton()
-        self.pf_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_1 = QtWidgets.QHBoxLayout()
-        self.form_box_child_1.addWidget(self.pf_color_entry)
-        self.form_box_child_1.addWidget(self.pf_color_button)
-        self.form_box_child_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+        self.fill_color_entry = FCColorEntry()
 
-        grid0.addWidget(self.pf_color_label, 12, 0)
-        grid0.addLayout(self.form_box_child_1, 12, 1, 1, 2)
+        grid0.addWidget(self.fill_color_label, 12, 0)
+        grid0.addWidget(self.fill_color_entry, 12, 1, 1, 2)
 
         # Plot Fill Transparency Level
-        self.pf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
-        self.pf_alpha_label.setToolTip(
+        self.gerber_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
+        self.gerber_alpha_label.setToolTip(
             _("Set the fill transparency for plotted objects.")
         )
-        self.pf_color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
-        self.pf_color_alpha_slider.setMinimum(0)
-        self.pf_color_alpha_slider.setMaximum(255)
-        self.pf_color_alpha_slider.setSingleStep(1)
+        self.gerber_alpha_entry = FCSliderWithSpinner(0, 255, 1)
 
-        self.pf_color_alpha_spinner = FCSpinner()
-        self.pf_color_alpha_spinner.setMinimumWidth(70)
-        self.pf_color_alpha_spinner.set_range(0, 255)
-
-        self.form_box_child_3 = QtWidgets.QHBoxLayout()
-        self.form_box_child_3.addWidget(self.pf_color_alpha_slider)
-        self.form_box_child_3.addWidget(self.pf_color_alpha_spinner)
-
-        grid0.addWidget(self.pf_alpha_label, 13, 0)
-        grid0.addLayout(self.form_box_child_3, 13, 1, 1, 2)
+        grid0.addWidget(self.gerber_alpha_label, 13, 0)
+        grid0.addWidget(self.gerber_alpha_entry, 13, 1, 1, 2)
 
         self.layout.addStretch()
 
         # Setting plot colors signals
-        self.pl_color_entry.editingFinished.connect(self.on_pl_color_entry)
-        self.pl_color_button.clicked.connect(self.on_pl_color_button)
-        self.pf_color_entry.editingFinished.connect(self.on_pf_color_entry)
-        self.pf_color_button.clicked.connect(self.on_pf_color_button)
-        self.pf_color_alpha_spinner.valueChanged.connect(self.on_pf_color_spinner)
-        self.pf_color_alpha_slider.valueChanged.connect(self.on_pf_color_slider)
-
-    # Setting plot colors handlers
-    def on_pf_color_entry(self):
-        self.app.defaults['gerber_plot_fill'] = self.pf_color_entry.get_value()[:7] + \
-            self.app.defaults['gerber_plot_fill'][7:9]
-        self.pf_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['gerber_plot_fill'])[:7])
-
-    def on_pf_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['gerber_plot_fill'][:7])
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_fill_color = c_dialog.getColor(initial=current_color)
-
-        if plot_fill_color.isValid() is False:
-            return
+        self.line_color_entry.editingFinished.connect(self.on_line_color_changed)
+        self.fill_color_entry.editingFinished.connect(self.on_fill_color_changed)
 
-        self.pf_color_button.setStyleSheet("background-color:%s" % str(plot_fill_color.name()))
+        self.gerber_alpha_entry.valueChanged.connect(self.on_gerber_alpha_changed)     # alpha
 
-        new_val = str(plot_fill_color.name()) + str(self.app.defaults['gerber_plot_fill'][7:9])
-        self.pf_color_entry.set_value(new_val)
-        self.app.defaults['gerber_plot_fill'] = new_val
+    # Setting plot colors handlers
+    def on_fill_color_changed(self):
+        self.app.defaults['gerber_plot_fill'] = self.fill_color_entry.get_value()[:7] + \
+                                                self.app.defaults['gerber_plot_fill'][7:9]
 
-    def on_pf_color_spinner(self):
-        spinner_value = self.pf_color_alpha_spinner.value()
-        self.pf_color_alpha_slider.setValue(spinner_value)
+    def on_gerber_alpha_changed(self, spinner_value):
         self.app.defaults['gerber_plot_fill'] = \
             self.app.defaults['gerber_plot_fill'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
@@ -247,27 +202,6 @@ class GerberGenPrefGroupUI(OptionsGroupUI):
             self.app.defaults['gerber_plot_line'][:7] + \
             (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
 
-    def on_pf_color_slider(self):
-        slider_value = self.pf_color_alpha_slider.value()
-        self.pf_color_alpha_spinner.setValue(slider_value)
-
-    def on_pl_color_entry(self):
-        self.app.defaults['gerber_plot_line'] = self.pl_color_entry.get_value()[:7] + \
+    def on_line_color_changed(self):
+        self.app.defaults['gerber_plot_line'] = self.line_color_entry.get_value()[:7] + \
                                                 self.app.defaults['gerber_plot_line'][7:9]
-        self.pl_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['gerber_plot_line'])[:7])
-
-    def on_pl_color_button(self):
-        current_color = QtGui.QColor(self.app.defaults['gerber_plot_line'][:7])
-        # print(current_color)
-
-        c_dialog = QtWidgets.QColorDialog()
-        plot_line_color = c_dialog.getColor(initial=current_color)
-
-        if plot_line_color.isValid() is False:
-            return
-
-        self.pl_color_button.setStyleSheet("background-color:%s" % str(plot_line_color.name()))
-
-        new_val_line = str(plot_line_color.name()) + str(self.app.defaults['gerber_plot_line'][7:9])
-        self.pl_color_entry.set_value(new_val_line)
-        self.app.defaults['gerber_plot_line'] = new_val_line

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

@@ -0,0 +1,100 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import QSettings
+
+from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox, FCComboBox
+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 GerberOptPrefGroupUI(OptionsGroupUI):
+    def __init__(self, decimals=4, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber Options Preferences", parent=parent)
+        super(GerberOptPrefGroupUI, self).__init__(self, parent=parent)
+
+        self.decimals = decimals
+
+        self.setTitle(str(_("Gerber Options")))
+
+        # ## Clear non-copper regions
+        self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
+        self.clearcopper_label.setToolTip(
+            _("Create polygons covering the\n"
+              "areas without copper on the PCB.\n"
+              "Equivalent to the inverse of this\n"
+              "object. Can be used to remove all\n"
+              "copper from a specified region.")
+        )
+        self.layout.addWidget(self.clearcopper_label)
+
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+
+        # Margin
+        bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
+        bmlabel.setToolTip(
+            _("Specify the edge of the PCB\n"
+              "by drawing a box around all\n"
+              "objects with this minimum\n"
+              "distance.")
+        )
+        grid1.addWidget(bmlabel, 0, 0)
+        self.noncopper_margin_entry = FCDoubleSpinner()
+        self.noncopper_margin_entry.set_precision(self.decimals)
+        self.noncopper_margin_entry.setSingleStep(0.1)
+        self.noncopper_margin_entry.set_range(-9999, 9999)
+        grid1.addWidget(self.noncopper_margin_entry, 0, 1)
+
+        # Rounded corners
+        self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
+        self.noncopper_rounded_cb.setToolTip(
+            _("Resulting geometry will have rounded corners.")
+        )
+        grid1.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid1.addWidget(separator_line, 2, 0, 1, 2)
+
+        # ## Bounding box
+        self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box'))
+        self.layout.addWidget(self.boundingbox_label)
+
+        grid2 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid2)
+
+        bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
+        bbmargin.setToolTip(
+            _("Distance of the edges of the box\n"
+              "to the nearest polygon.")
+        )
+        self.bbmargin_entry = FCDoubleSpinner()
+        self.bbmargin_entry.set_precision(self.decimals)
+        self.bbmargin_entry.setSingleStep(0.1)
+        self.bbmargin_entry.set_range(-9999, 9999)
+
+        grid2.addWidget(bbmargin, 0, 0)
+        grid2.addWidget(self.bbmargin_entry, 0, 1)
+
+        self.bbrounded_cb = FCCheckBox(label='%s' % _("Rounded Geo"))
+        self.bbrounded_cb.setToolTip(
+            _("If the bounding box is \n"
+              "to have rounded corners\n"
+              "their radius is equal to\n"
+              "the margin.")
+        )
+        grid2.addWidget(self.bbrounded_cb, 1, 0, 1, 2)
+        self.layout.addStretch()

+ 7 - 6
flatcamGUI/preferences/gerber/GerberPreferencesUI.py → AppGUI/preferences/gerber/GerberPreferencesUI.py

@@ -1,14 +1,14 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.preferences.gerber.GerberEditorPrefGroupUI import GerberEditorPrefGroupUI
-from flatcamGUI.preferences.gerber.GerberExpPrefGroupUI import GerberExpPrefGroupUI
-from flatcamGUI.preferences.gerber.GerberAdvOptPrefGroupUI import GerberAdvOptPrefGroupUI
-from flatcamGUI.preferences.gerber.GerberOptPrefGroupUI import GerberOptPrefGroupUI
-from flatcamGUI.preferences.gerber.GerberGenPrefGroupUI import GerberGenPrefGroupUI
+from AppGUI.preferences.gerber.GerberEditorPrefGroupUI import GerberEditorPrefGroupUI
+from AppGUI.preferences.gerber.GerberExpPrefGroupUI import GerberExpPrefGroupUI
+from AppGUI.preferences.gerber.GerberAdvOptPrefGroupUI import GerberAdvOptPrefGroupUI
+from AppGUI.preferences.gerber.GerberOptPrefGroupUI import GerberOptPrefGroupUI
+from AppGUI.preferences.gerber.GerberGenPrefGroupUI import GerberGenPrefGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)

+ 0 - 0
flatcamGUI/preferences/gerber/__init__.py → AppGUI/preferences/gerber/__init__.py


+ 3 - 3
flatcamGUI/preferences/tools/Tools2CThievingPrefGroupUI.py → AppGUI/preferences/tools/Tools2CThievingPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 4 - 4
flatcamGUI/preferences/tools/Tools2CalPrefGroupUI.py → AppGUI/preferences/tools/Tools2CalPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)

+ 3 - 3
flatcamGUI/preferences/tools/Tools2EDrillsPrefGroupUI.py → AppGUI/preferences/tools/Tools2EDrillsPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/tools/Tools2FiducialsPrefGroupUI.py → AppGUI/preferences/tools/Tools2FiducialsPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, RadioSet
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/tools/Tools2InvertPrefGroupUI.py → AppGUI/preferences/tools/Tools2InvertPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, RadioSet
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/tools/Tools2OptimalPrefGroupUI.py → AppGUI/preferences/tools/Tools2OptimalPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCSpinner
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCSpinner
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 10 - 10
flatcamGUI/preferences/tools/Tools2PreferencesUI.py → AppGUI/preferences/tools/Tools2PreferencesUI.py

@@ -1,18 +1,18 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.preferences.tools.Tools2InvertPrefGroupUI import Tools2InvertPrefGroupUI
-from flatcamGUI.preferences.tools.Tools2PunchGerberPrefGroupUI import Tools2PunchGerberPrefGroupUI
-from flatcamGUI.preferences.tools.Tools2EDrillsPrefGroupUI import Tools2EDrillsPrefGroupUI
-from flatcamGUI.preferences.tools.Tools2CalPrefGroupUI import Tools2CalPrefGroupUI
-from flatcamGUI.preferences.tools.Tools2FiducialsPrefGroupUI import Tools2FiducialsPrefGroupUI
-from flatcamGUI.preferences.tools.Tools2CThievingPrefGroupUI import Tools2CThievingPrefGroupUI
-from flatcamGUI.preferences.tools.Tools2QRCodePrefGroupUI import Tools2QRCodePrefGroupUI
-from flatcamGUI.preferences.tools.Tools2OptimalPrefGroupUI import Tools2OptimalPrefGroupUI
-from flatcamGUI.preferences.tools.Tools2RulesCheckPrefGroupUI import Tools2RulesCheckPrefGroupUI
+from AppGUI.preferences.tools.Tools2InvertPrefGroupUI import Tools2InvertPrefGroupUI
+from AppGUI.preferences.tools.Tools2PunchGerberPrefGroupUI import Tools2PunchGerberPrefGroupUI
+from AppGUI.preferences.tools.Tools2EDrillsPrefGroupUI import Tools2EDrillsPrefGroupUI
+from AppGUI.preferences.tools.Tools2CalPrefGroupUI import Tools2CalPrefGroupUI
+from AppGUI.preferences.tools.Tools2FiducialsPrefGroupUI import Tools2FiducialsPrefGroupUI
+from AppGUI.preferences.tools.Tools2CThievingPrefGroupUI import Tools2CThievingPrefGroupUI
+from AppGUI.preferences.tools.Tools2QRCodePrefGroupUI import Tools2QRCodePrefGroupUI
+from AppGUI.preferences.tools.Tools2OptimalPrefGroupUI import Tools2OptimalPrefGroupUI
+from AppGUI.preferences.tools.Tools2RulesCheckPrefGroupUI import Tools2RulesCheckPrefGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/tools/Tools2PunchGerberPrefGroupUI.py → AppGUI/preferences/tools/Tools2PunchGerberPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 18 - 30
flatcamGUI/preferences/tools/Tools2QRCodePrefGroupUI.py → AppGUI/preferences/tools/Tools2QRCodePrefGroupUI.py

@@ -1,11 +1,11 @@
-from PyQt5 import QtWidgets, QtCore
+from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import Qt, QSettings
 
-from flatcamGUI.GUIElements import FCSpinner, RadioSet, FCTextArea, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCSpinner, RadioSet, FCTextArea, FCEntry, FCColorEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -153,42 +153,20 @@ class Tools2QRCodePrefGroupUI(OptionsGroupUI):
         self.fill_color_label.setToolTip(
             _("Set the QRCode fill color (squares color).")
         )
-        self.fill_color_entry = FCEntry()
-        self.fill_color_button = QtWidgets.QPushButton()
-        self.fill_color_button.setFixedSize(15, 15)
-
-        fill_lay_child = QtWidgets.QHBoxLayout()
-        fill_lay_child.setContentsMargins(0, 0, 0, 0)
-        fill_lay_child.addWidget(self.fill_color_entry)
-        fill_lay_child.addWidget(self.fill_color_button, alignment=Qt.AlignRight)
-        fill_lay_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        fill_color_widget = QtWidgets.QWidget()
-        fill_color_widget.setLayout(fill_lay_child)
+        self.fill_color_entry = FCColorEntry()
 
         grid_lay.addWidget(self.fill_color_label, 9, 0)
-        grid_lay.addWidget(fill_color_widget, 9, 1)
+        grid_lay.addWidget(self.fill_color_entry, 9, 1)
 
         # BACK COLOR #
         self.back_color_label = QtWidgets.QLabel('%s:' % _('Back Color'))
         self.back_color_label.setToolTip(
             _("Set the QRCode background color.")
         )
-        self.back_color_entry = FCEntry()
-        self.back_color_button = QtWidgets.QPushButton()
-        self.back_color_button.setFixedSize(15, 15)
-
-        back_lay_child = QtWidgets.QHBoxLayout()
-        back_lay_child.setContentsMargins(0, 0, 0, 0)
-        back_lay_child.addWidget(self.back_color_entry)
-        back_lay_child.addWidget(self.back_color_button, alignment=Qt.AlignRight)
-        back_lay_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        back_color_widget = QtWidgets.QWidget()
-        back_color_widget.setLayout(back_lay_child)
+        self.back_color_entry = FCColorEntry()
 
         grid_lay.addWidget(self.back_color_label, 10, 0)
-        grid_lay.addWidget(back_color_widget, 10, 1)
+        grid_lay.addWidget(self.back_color_entry, 10, 1)
 
         # Selection Limit
         self.sel_limit_label = QtWidgets.QLabel('%s:' % _("Selection limit"))
@@ -205,3 +183,13 @@ class Tools2QRCodePrefGroupUI(OptionsGroupUI):
         grid_lay.addWidget(self.sel_limit_label, 11, 0)
         grid_lay.addWidget(self.sel_limit_entry, 11, 1)
         # self.layout.addStretch()
+
+        # QRCode Tool
+        self.fill_color_entry.editingFinished.connect(self.on_qrcode_fill_color_entry)
+        self.back_color_entry.editingFinished.connect(self.on_qrcode_back_color_entry)
+
+    def on_qrcode_fill_color_entry(self):
+        self.app.defaults['tools_qrcode_fill_color'] = self.fill_color_entry.get_value()
+
+    def on_qrcode_back_color_entry(self):
+        self.app.defaults['tools_qrcode_back_color'] = self.back_color_entry.get_value()

+ 3 - 3
flatcamGUI/preferences/tools/Tools2RulesCheckPrefGroupUI.py → AppGUI/preferences/tools/Tools2RulesCheckPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCCheckBox, FCDoubleSpinner
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/tools/Tools2sidedPrefGroupUI.py → AppGUI/preferences/tools/Tools2sidedPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, RadioSet
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/tools/ToolsCalculatorsPrefGroupUI.py → AppGUI/preferences/tools/ToolsCalculatorsPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 81 - 0
AppGUI/preferences/tools/ToolsCornersPrefGroupUI.py

@@ -0,0 +1,81 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import QSettings
+
+from AppGUI.GUIElements import FCDoubleSpinner
+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 ToolsCornersPrefGroupUI(OptionsGroupUI):
+    def __init__(self, decimals=4, parent=None):
+        # OptionsGroupUI.__init__(self, "Calculators Tool Options", parent=parent)
+        super(ToolsCornersPrefGroupUI, self).__init__(self, parent=parent)
+
+        self.setTitle(str(_("Corner Markers Options")))
+        self.decimals = decimals
+
+        grid0 = QtWidgets.QGridLayout()
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
+        self.layout.addLayout(grid0)
+
+        self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
+        self.param_label.setToolTip(
+            _("Parameters used for this tool.")
+        )
+        grid0.addWidget(self.param_label, 0, 0, 1, 2)
+
+        # Thickness #
+        self.thick_label = QtWidgets.QLabel('%s:' % _("Thickness"))
+        self.thick_label.setToolTip(
+            _("The thickness of the line that makes the corner marker.")
+        )
+        self.thick_entry = FCDoubleSpinner()
+        self.thick_entry.set_range(0.0000, 9.9999)
+        self.thick_entry.set_precision(self.decimals)
+        self.thick_entry.setWrapping(True)
+        self.thick_entry.setSingleStep(10 ** -self.decimals)
+
+        grid0.addWidget(self.thick_label, 1, 0)
+        grid0.addWidget(self.thick_entry, 1, 1)
+
+        # Length #
+        self.l_label = QtWidgets.QLabel('%s:' % _("Length"))
+        self.l_label.setToolTip(
+            _("The length of the line that makes the corner marker.")
+        )
+        self.l_entry = FCDoubleSpinner()
+        self.l_entry.set_range(-9999.9999, 9999.9999)
+        self.l_entry.set_precision(self.decimals)
+        self.l_entry.setSingleStep(10 ** -self.decimals)
+
+        # Margin #
+        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
+        self.margin_label.setToolTip(
+            _("Bounding box margin.")
+        )
+        self.margin_entry = FCDoubleSpinner()
+        self.margin_entry.set_range(-9999.9999, 9999.9999)
+        self.margin_entry.set_precision(self.decimals)
+        self.margin_entry.setSingleStep(0.1)
+
+        grid0.addWidget(self.margin_label, 2, 0)
+        grid0.addWidget(self.margin_entry, 2, 1)
+
+        grid0.addWidget(self.l_label, 4, 0)
+        grid0.addWidget(self.l_entry, 4, 1)
+
+        self.layout.addStretch()

+ 4 - 4
flatcamGUI/preferences/tools/ToolsCutoutPrefGroupUI.py → AppGUI/preferences/tools/ToolsCutoutPrefGroupUI.py

@@ -1,12 +1,12 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox
-from flatcamGUI.preferences import machinist_setting
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox
+from AppGUI.preferences import machinist_setting
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 14 - 19
flatcamGUI/preferences/tools/ToolsFilmPrefGroupUI.py → AppGUI/preferences/tools/ToolsFilmPrefGroupUI.py

@@ -1,11 +1,11 @@
-from PyQt5 import QtWidgets, QtCore
+from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import Qt, QSettings
 
-from flatcamGUI.GUIElements import RadioSet, FCEntry, FCDoubleSpinner, FCCheckBox, FCComboBox
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import RadioSet, FCEntry, FCDoubleSpinner, FCCheckBox, FCComboBox, FCColorEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -30,8 +30,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
         # ## Parameters
         self.film_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
         self.film_label.setToolTip(
-            _("Create a PCB film from a Gerber or Geometry\n"
-              "FlatCAM object.\n"
+            _("Create a PCB film from a Gerber or Geometry object.\n"
               "The file is saved in SVG format.")
         )
         self.layout.addWidget(self.film_label)
@@ -58,20 +57,10 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
         self.film_color_label.setToolTip(
             _("Set the film color when positive film is selected.")
         )
-        self.film_color_entry = FCEntry()
-        self.film_color_button = QtWidgets.QPushButton()
-        self.film_color_button.setFixedSize(15, 15)
-
-        self.form_box_child = QtWidgets.QHBoxLayout()
-        self.form_box_child.setContentsMargins(0, 0, 0, 0)
-        self.form_box_child.addWidget(self.film_color_entry)
-        self.form_box_child.addWidget(self.film_color_button, alignment=Qt.AlignRight)
-        self.form_box_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        film_color_widget = QtWidgets.QWidget()
-        film_color_widget.setLayout(self.form_box_child)
+        self.film_color_entry = FCColorEntry()
+
         grid0.addWidget(self.film_color_label, 1, 0)
-        grid0.addWidget(film_color_widget, 1, 1)
+        grid0.addWidget(self.film_color_entry, 1, 1)
 
         # Film Border
         self.film_boundary_entry = FCDoubleSpinner()
@@ -314,3 +303,9 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.pagesize_combo, 17, 1)
 
         self.layout.addStretch()
+
+        # Film Tool
+        self.film_color_entry.editingFinished.connect(self.on_film_color_entry)
+
+    def on_film_color_entry(self):
+        self.app.defaults['tools_film_color'] = self.film_color_entry.get_value()

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

@@ -0,0 +1,320 @@
+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"
+              "- 'Area Selection' -> Isolate polygons within a selection area.\n"
+              "- 'Polygon Selection' -> Isolate a selection of polygons.\n"
+              "- 'Reference Object' - will process the area specified by another object.")
+        )
+        self.select_combo = FCComboBox()
+        self.select_combo.addItems(
+            [_("All"), _("Area Selection"), _("Polygon Selection"), _("Reference Object")]
+        )
+        self.select_combo.setObjectName("i_selection")
+
+        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()

+ 12 - 12
flatcamGUI/preferences/tools/ToolsNCCPrefGroupUI.py → AppGUI/preferences/tools/ToolsNCCPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)
 
@@ -227,7 +227,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         # ], orientation='vertical', stretch=False)
         self.ncc_method_combo = FCComboBox()
         self.ncc_method_combo.addItems(
-            [_("Standard"), _("Seed"), _("Lines")]
+            [_("Standard"), _("Seed"), _("Lines"), _("Combo")]
         )
 
         grid0.addWidget(methodlabel, 12, 0)
@@ -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()

+ 12 - 12
flatcamGUI/preferences/tools/ToolsPaintPrefGroupUI.py → AppGUI/preferences/tools/ToolsPaintPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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)
@@ -105,7 +105,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
         cutzlabel.setToolTip(
             _("Depth of cut into material. Negative value.\n"
-              "In FlatCAM units.")
+              "In application units.")
         )
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry.set_precision(self.decimals)
@@ -114,7 +114,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
 
         self.cutz_entry.setToolTip(
             _("Depth of cut into material. Negative value.\n"
-              "In FlatCAM units.")
+              "In application units.")
         )
         grid0.addWidget(cutzlabel, 4, 0)
         grid0.addWidget(self.cutz_entry, 4, 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)

+ 3 - 3
flatcamGUI/preferences/tools/ToolsPanelizePrefGroupUI.py → AppGUI/preferences/tools/ToolsPanelizePrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 23 - 12
flatcamGUI/preferences/tools/ToolsPreferencesUI.py → AppGUI/preferences/tools/ToolsPreferencesUI.py

@@ -1,19 +1,22 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.preferences.tools.ToolsSubPrefGroupUI import ToolsSubPrefGroupUI
-from flatcamGUI.preferences.tools.ToolsSolderpastePrefGroupUI import ToolsSolderpastePrefGroupUI
-from flatcamGUI.preferences.tools.ToolsTransformPrefGroupUI import ToolsTransformPrefGroupUI
-from flatcamGUI.preferences.tools.ToolsCalculatorsPrefGroupUI import ToolsCalculatorsPrefGroupUI
-from flatcamGUI.preferences.tools.ToolsPanelizePrefGroupUI import ToolsPanelizePrefGroupUI
-from flatcamGUI.preferences.tools.ToolsFilmPrefGroupUI import ToolsFilmPrefGroupUI
-from flatcamGUI.preferences.tools.ToolsPaintPrefGroupUI import ToolsPaintPrefGroupUI
-from flatcamGUI.preferences.tools.Tools2sidedPrefGroupUI import Tools2sidedPrefGroupUI
-from flatcamGUI.preferences.tools.ToolsCutoutPrefGroupUI import ToolsCutoutPrefGroupUI
-from flatcamGUI.preferences.tools.ToolsNCCPrefGroupUI import ToolsNCCPrefGroupUI
+from AppGUI.preferences.tools.ToolsSubPrefGroupUI import ToolsSubPrefGroupUI
+from AppGUI.preferences.tools.ToolsSolderpastePrefGroupUI import ToolsSolderpastePrefGroupUI
+from AppGUI.preferences.tools.ToolsCornersPrefGroupUI import ToolsCornersPrefGroupUI
+from AppGUI.preferences.tools.ToolsTransformPrefGroupUI import ToolsTransformPrefGroupUI
+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.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 FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -35,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)
 
@@ -62,6 +68,9 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
         self.tools_solderpaste_group = ToolsSolderpastePrefGroupUI(decimals=self.decimals)
         self.tools_solderpaste_group.setMinimumWidth(200)
 
+        self.tools_corners_group = ToolsCornersPrefGroupUI(decimals=self.decimals)
+        self.tools_corners_group.setMinimumWidth(200)
+
         self.tools_sub_group = ToolsSubPrefGroupUI(decimals=self.decimals)
         self.tools_sub_group.setMinimumWidth(200)
 
@@ -71,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)
@@ -84,6 +93,8 @@ 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)

+ 5 - 5
flatcamGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py → AppGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCEntry, FCDoubleSpinner, FCSpinner, FCComboBox
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, FCComboBox, NumericalEvalTupleEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -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"

+ 3 - 3
flatcamGUI/preferences/tools/ToolsSubPrefGroupUI.py → AppGUI/preferences/tools/ToolsSubPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCCheckBox
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCCheckBox
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 5 - 5
flatcamGUI/preferences/tools/ToolsTransformPrefGroupUI.py → AppGUI/preferences/tools/ToolsTransformPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -31,7 +31,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
         self.transform_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
         self.transform_label.setToolTip(
             _("Various transformations that can be applied\n"
-              "on a FlatCAM object.")
+              "on a application object.")
         )
         self.layout.addWidget(self.transform_label)
 
@@ -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)

+ 0 - 0
flatcamGUI/preferences/tools/__init__.py → AppGUI/preferences/tools/__init__.py


+ 3 - 3
flatcamGUI/preferences/utilities/AutoCompletePrefGroupUI.py → AppGUI/preferences/utilities/AutoCompletePrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets, QtGui
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCButton, FCTextArea, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCButton, FCTextArea, FCEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/utilities/FAExcPrefGroupUI.py → AppGUI/preferences/utilities/FAExcPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets, QtGui
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import VerticalScrollArea, FCButton, FCTextArea, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import VerticalScrollArea, FCButton, FCTextArea, FCEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/utilities/FAGcoPrefGroupUI.py → AppGUI/preferences/utilities/FAGcoPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets, QtGui
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCButton, FCTextArea, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCButton, FCTextArea, FCEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 3 - 3
flatcamGUI/preferences/utilities/FAGrbPrefGroupUI.py → AppGUI/preferences/utilities/FAGrbPrefGroupUI.py

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets, QtGui
 from PyQt5.QtCore import QSettings
 
-from flatcamGUI.GUIElements import FCButton, FCTextArea, FCEntry
-from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from AppGUI.GUIElements import FCButton, FCTextArea, FCEntry
+from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 4 - 4
flatcamGUI/preferences/utilities/UtilPreferencesUI.py → AppGUI/preferences/utilities/UtilPreferencesUI.py

@@ -1,9 +1,9 @@
 from PyQt5 import QtWidgets
 
-from flatcamGUI.preferences.utilities.AutoCompletePrefGroupUI import AutoCompletePrefGroupUI
-from flatcamGUI.preferences.utilities.FAGrbPrefGroupUI import FAGrbPrefGroupUI
-from flatcamGUI.preferences.utilities.FAGcoPrefGroupUI import FAGcoPrefGroupUI
-from flatcamGUI.preferences.utilities.FAExcPrefGroupUI import FAExcPrefGroupUI
+from AppGUI.preferences.utilities.AutoCompletePrefGroupUI import AutoCompletePrefGroupUI
+from AppGUI.preferences.utilities.FAGrbPrefGroupUI import FAGrbPrefGroupUI
+from AppGUI.preferences.utilities.FAGcoPrefGroupUI import FAGcoPrefGroupUI
+from AppGUI.preferences.utilities.FAExcPrefGroupUI import FAExcPrefGroupUI
 
 
 class UtilPreferencesUI(QtWidgets.QWidget):

+ 0 - 0
flatcamGUI/preferences/utilities/__init__.py → AppGUI/preferences/utilities/__init__.py


+ 394 - 0
AppObjects/AppObject.py

@@ -0,0 +1,394 @@
+# ###########################################################
+# FlatCAM: 2D Post-processing for Manufacturing             #
+# http://flatcam.org                                        #
+# Author: Juan Pablo Caram (c)                              #
+# Date: 2/5/2014                                            #
+# MIT Licence                                               #
+# Modified by Marius Stanciu (2020)                         #
+# ###########################################################
+
+from PyQt5 import QtCore
+from AppObjects.ObjectCollection import *
+from AppObjects.FlatCAMCNCJob import CNCJobObject
+from AppObjects.FlatCAMDocument import DocumentObject
+from AppObjects.FlatCAMExcellon import ExcellonObject
+from AppObjects.FlatCAMGeometry import GeometryObject
+from AppObjects.FlatCAMGerber import GerberObject
+from AppObjects.FlatCAMScript import ScriptObject
+
+import time
+import traceback
+
+# FlatCAM Translation
+import gettext
+import AppTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class AppObject(QtCore.QObject):
+
+    # Emitted by app_obj.new_object() and passes the new object as argument, plot flag.
+    # on_object_created() adds the object to the collection, plots on appropriate flag
+    # and emits app_obj.new_object_available.
+    object_created = QtCore.pyqtSignal(object, bool, bool)
+
+    # Emitted when a object has been changed (like scaled, mirrored)
+    object_changed = QtCore.pyqtSignal(object)
+
+    # Emitted after object has been plotted.
+    # Calls 'on_zoom_fit' method to fit object in scene view in main thread to prevent drawing glitches.
+    object_plotted = QtCore.pyqtSignal(object)
+
+    plots_updated = QtCore.pyqtSignal()
+
+    def __init__(self, app):
+        super(AppObject, self).__init__()
+        self.app = app
+        self.inform = app.inform
+
+        # signals that are emitted when object state changes
+        self.object_created.connect(self.on_object_created)
+        self.object_changed.connect(self.on_object_changed)
+        self.object_plotted.connect(self.on_object_plotted)
+        self.plots_updated.connect(self.app.on_plots_updated)
+
+    def new_object(self, kind, name, initialize, plot=True, autoselected=True):
+        """
+        Creates a new specialized FlatCAMObj and attaches it to the application,
+        this is, updates the GUI accordingly, any other records and plots it.
+        This method is thread-safe.
+
+        Notes:
+            * If the name is in use, the self.collection will modify it
+              when appending it to the collection. There is no need to handle
+              name conflicts here.
+
+        :param kind: The kind of object to create. One of 'gerber', 'excellon', 'cncjob' and 'geometry'.
+        :type kind: str
+        :param name: Name for the object.
+        :type name: str
+        :param initialize: Function to run after creation of the object but before it is attached to the application.
+        The function is called with 2 parameters: the new object and the App instance.
+        :type initialize: function
+        :param plot: If to plot the resulting object
+        :param autoselected: if the resulting object is autoselected in the Project tab and therefore in the
+        self.collection
+        :return: None
+        :rtype: None
+        """
+
+        log.debug("AppObject.new_object()")
+        obj_plot = plot
+        obj_autoselected = autoselected
+
+        t0 = time.time()  # Debug
+
+        # ## Create object
+        classdict = {
+            "gerber": GerberObject,
+            "excellon": ExcellonObject,
+            "cncjob": CNCJobObject,
+            "geometry": GeometryObject,
+            "script": ScriptObject,
+            "document": DocumentObject
+        }
+
+        log.debug("Calling object constructor...")
+
+        # Object creation/instantiation
+        obj = classdict[kind](name)
+
+        obj.units = self.app.options["units"]
+
+        # IMPORTANT
+        # The key names in defaults and options dictionary's are not random:
+        # they have to have in name first the type of the object (geometry, excellon, cncjob and gerber) or how it's
+        # called here, the 'kind' followed by an underline. Above the App default values from self.defaults are
+        # copied to self.options. After that, below, depending on the type of
+        # object that is created, it will strip the name of the object and the underline (if the original key was
+        # let's say "excellon_toolchange", it will strip the excellon_) and to the obj.options the key will become
+        # "toolchange"
+
+        for option in self.app.options:
+            if option.find(kind + "_") == 0:
+                oname = option[len(kind) + 1:]
+                obj.options[oname] = self.app.options[option]
+
+        obj.isHovering = False
+        obj.notHovering = True
+
+        # Initialize as per user request
+        # User must take care to implement initialize
+        # in a thread-safe way as is is likely that we
+        # have been invoked in a separate thread.
+        t1 = time.time()
+        log.debug("%f seconds before initialize()." % (t1 - t0))
+        try:
+            return_value = initialize(obj, self.app)
+        except Exception as e:
+            msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n")
+            msg += _("Object ({kind}) failed because: {error} \n\n").format(kind=kind, error=str(e))
+            msg += traceback.format_exc()
+            self.app.inform.emit(msg)
+            return "fail"
+
+        t2 = time.time()
+        log.debug("%f seconds executing initialize()." % (t2 - t1))
+
+        if return_value == 'fail':
+            log.debug("Object (%s) parsing and/or geometry creation failed." % kind)
+            return "fail"
+
+        # Check units and convert if necessary
+        # This condition CAN be true because initialize() can change obj.units
+        if self.app.options["units"].upper() != obj.units.upper():
+            self.app.inform.emit('%s: %s' % (_("Converting units to "), self.app.options["units"]))
+            obj.convert_units(self.app.options["units"])
+            t3 = time.time()
+            log.debug("%f seconds converting units." % (t3 - t2))
+
+        # Create the bounding box for the object and then add the results to the obj.options
+        # But not for Scripts or for Documents
+        if kind != 'document' and kind != 'script':
+            try:
+                xmin, ymin, xmax, ymax = obj.bounds()
+                obj.options['xmin'] = xmin
+                obj.options['ymin'] = ymin
+                obj.options['xmax'] = xmax
+                obj.options['ymax'] = ymax
+            except Exception as e:
+                log.warning("AppObject.new_object() -> The object has no bounds properties. %s" % str(e))
+                return "fail"
+
+            try:
+                if kind == 'excellon':
+                    obj.fill_color = self.app.defaults["excellon_plot_fill"]
+                    obj.outline_color = self.app.defaults["excellon_plot_line"]
+
+                if kind == 'gerber':
+                    obj.fill_color = self.app.defaults["gerber_plot_fill"]
+                    obj.outline_color = self.app.defaults["gerber_plot_line"]
+            except Exception as e:
+                log.warning("AppObject.new_object() -> setting colors error. %s" % str(e))
+
+        # update the KeyWords list with the name of the file
+        self.app.myKeywords.append(obj.options['name'])
+
+        log.debug("Moving new object back to main thread.")
+
+        # Move the object to the main thread and let the app know that it is available.
+        obj.moveToThread(self.app.main_thread)
+        self.object_created.emit(obj, obj_plot, obj_autoselected)
+
+        return obj
+
+    def new_excellon_object(self):
+        """
+        Creates a new, blank Excellon object.
+
+        :return: None
+        """
+
+        self.new_object('excellon', 'new_exc', lambda x, y: None, plot=False)
+
+    def new_geometry_object(self):
+        """
+        Creates a new, blank and single-tool Geometry object.
+
+        :return: None
+        """
+
+        def initialize(obj, app):
+            obj.multitool = False
+
+        self.new_object('geometry', 'new_geo', initialize, plot=False)
+
+    def new_gerber_object(self):
+        """
+        Creates a new, blank Gerber object.
+
+        :return: None
+        """
+
+        def initialize(grb_obj, app):
+            grb_obj.multitool = False
+            grb_obj.source_file = []
+            grb_obj.multigeo = False
+            grb_obj.follow = False
+            grb_obj.apertures = {}
+            grb_obj.solid_geometry = []
+
+            try:
+                grb_obj.options['xmin'] = 0
+                grb_obj.options['ymin'] = 0
+                grb_obj.options['xmax'] = 0
+                grb_obj.options['ymax'] = 0
+            except KeyError:
+                pass
+
+        self.new_object('gerber', 'new_grb', initialize, plot=False)
+
+    def new_script_object(self):
+        """
+        Creates a new, blank TCL Script object.
+
+        :return: None
+        """
+
+        # commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \
+        #                 "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \
+        #                 "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \
+        #                 "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n" \
+        #                 "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \
+        #                 "ListSys, MillDrills,\n" \
+        #                 "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \
+        #                 "Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" \
+        #                 "# Options, Paint, Panelize, PlotAl, PlotObjects, SaveProject, " \
+        #                 "SaveSys, Scale, SetActive, SetSys, SetOrigin, Skew, SubtractPoly,\n" \
+        #                 "# SubtractRectangle, Version, WriteGCode\n"
+
+        new_source_file = '# %s\n' % _('CREATE A NEW FLATCAM TCL SCRIPT') + \
+                          '# %s:\n' % _('TCL Tutorial is here') + \
+                          '# https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n' + '\n\n' + \
+                          '# %s:\n' % _("FlatCAM commands list")
+        new_source_file += '# %s\n\n' % _("Type >help< followed by Run Code for a list of FlatCAM Tcl Commands "
+                                          "(displayed in Tcl Shell).")
+
+        def initialize(obj, app):
+            obj.source_file = deepcopy(new_source_file)
+
+        outname = 'new_script'
+        self.new_object('script', outname, initialize, plot=False)
+
+    def new_document_object(self):
+        """
+        Creates a new, blank Document object.
+
+        :return: None
+        """
+
+        def initialize(obj, app):
+            obj.source_file = ""
+
+        self.new_object('document', 'new_document', initialize, plot=False)
+
+    def on_object_created(self, obj, plot, auto_select):
+        """
+        Event callback for object creation.
+        It will add the new object to the collection. After that it will plot the object in a threaded way
+
+        :param obj: The newly created FlatCAM object.
+        :param plot: if the newly create object t obe plotted
+        :param auto_select: if the newly created object to be autoselected after creation
+        :return: None
+        """
+        t0 = time.time()  # DEBUG
+        log.debug("on_object_created()")
+
+        # The Collection might change the name if there is a collision
+        self.app.collection.append(obj)
+
+        # after adding the object to the collection always update the list of objects that are in the collection
+        self.app.all_objects_list = self.app.collection.get_list()
+
+        # self.app.inform.emit('[selected] %s created & selected: %s' %
+        #                  (str(obj.kind).capitalize(), str(obj.options['name'])))
+        if obj.kind == 'gerber':
+            self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
+                kind=obj.kind.capitalize(),
+                color='green',
+                name=str(obj.options['name']), tx=_("created/selected"))
+            )
+        elif obj.kind == 'excellon':
+            self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
+                kind=obj.kind.capitalize(),
+                color='brown',
+                name=str(obj.options['name']), tx=_("created/selected"))
+            )
+        elif obj.kind == 'cncjob':
+            self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
+                kind=obj.kind.capitalize(),
+                color='blue',
+                name=str(obj.options['name']), tx=_("created/selected"))
+            )
+        elif obj.kind == 'geometry':
+            self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
+                kind=obj.kind.capitalize(),
+                color='red',
+                name=str(obj.options['name']), tx=_("created/selected"))
+            )
+        elif obj.kind == 'script':
+            self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
+                kind=obj.kind.capitalize(),
+                color='orange',
+                name=str(obj.options['name']), tx=_("created/selected"))
+            )
+        elif obj.kind == 'document':
+            self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
+                kind=obj.kind.capitalize(),
+                color='darkCyan',
+                name=str(obj.options['name']), tx=_("created/selected"))
+            )
+
+        # update the SHELL auto-completer model with the name of the new object
+        self.app.shell._edit.set_model_data(self.app.myKeywords)
+
+        if auto_select:
+            # select the just opened object but deselect the previous ones
+            self.app.collection.set_all_inactive()
+            self.app.collection.set_active(obj.options["name"])
+        else:
+            self.app.collection.set_all_inactive()
+
+        # here it is done the object plotting
+        def task(t_obj):
+            with self.app.proc_container.new(_("Plotting")):
+                if t_obj.kind == 'cncjob':
+                    t_obj.plot(kind=self.app.defaults["cncjob_plot_kind"])
+                else:
+                    t_obj.plot()
+
+                t1 = time.time()  # DEBUG
+                log.debug("%f seconds adding object and plotting." % (t1 - t0))
+                self.object_plotted.emit(t_obj)
+
+        # Send to worker
+        # self.worker.add_task(worker_task, [self])
+        if plot is True:
+            self.app.worker_task.emit({'fcn': task, 'params': [obj]})
+
+    def on_object_changed(self, obj):
+        """
+        Called whenever the geometry of the object was changed in some way.
+        This require the update of it's bounding values so it can be the selected on canvas.
+        Update the bounding box data from obj.options
+
+        :param obj: the object that was changed
+        :return: None
+        """
+
+        try:
+            xmin, ymin, xmax, ymax = obj.bounds()
+        except TypeError:
+            return
+        obj.options['xmin'] = xmin
+        obj.options['ymin'] = ymin
+        obj.options['xmax'] = xmax
+        obj.options['ymax'] = ymax
+
+        log.debug("Object changed, updating the bounding box data on self.options")
+        # delete the old selection shape
+        self.app.delete_selection_shape()
+        self.app.should_we_save = True
+
+    def on_object_plotted(self):
+        """
+        Callback called whenever the plotted object needs to be fit into the viewport (canvas)
+
+        :return: None
+        """
+        self.app.on_zoom_fit()

+ 20 - 17
flatcamObjects/FlatCAMCNCJob.py → AppObjects/FlatCAMCNCJob.py

@@ -14,8 +14,8 @@ from copy import deepcopy
 from io import StringIO
 from datetime import datetime
 
-from flatcamEditors.FlatCAMTextEditor import TextEditor
-from flatcamObjects.FlatCAMObj import *
+from AppEditors.FlatCAMTextEditor import TextEditor
+from AppObjects.FlatCAMObj import *
 
 from camlib import CNCjob
 
@@ -24,7 +24,7 @@ import sys
 import math
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -504,17 +504,17 @@ class CNCJobObject(FlatCAMObj, CNCjob):
         try:
             dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name)
             filename, _f = FCFileSaveDialog.get_saved_filename(
-                caption=_("Export Machine Code ..."),
+                caption=_("Export 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 Code ..."), ext_filter=_filter_)
 
         filename = str(filename)
 
         if filename == '':
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Machine Code cancelled ..."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export cancelled ..."))
             return
         else:
             if save_gcode is True:
@@ -535,7 +535,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
         if self.app.defaults["global_open_style"] is False:
             self.app.file_opened.emit("gcode", filename)
         self.app.file_saved.emit("gcode", filename)
-        self.app.inform.emit('[success] %s: %s' % (_("Machine Code file saved to"), filename))
+        self.app.inform.emit('[success] %s: %s' % (_("File saved to"), filename))
 
     def on_edit_code_click(self, *args):
         """
@@ -564,7 +564,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
 
         # delete the absolute and relative position and messages in the infobar
         self.app.ui.position_label.setText("")
-        self.app.ui.rel_position_label.setText("")
+        # self.app.ui.rel_position_label.setText("")
 
         # first clear previous text in text editor (if any)
         self.gcode_editor_tab.code_editor.clear()
@@ -989,7 +989,6 @@ class CNCJobObject(FlatCAMObj, CNCjob):
                 for key in self.cnc_tools:
                     ppg = self.cnc_tools[key]['data']['ppname_g']
                     if 'toolchange_custom' not in str(ppg).lower():
-                        print(ppg)
                         if self.ui.toolchange_cb.get_value():
                             self.ui.toolchange_cb.set_value(False)
                             self.app.inform.emit('[WARNING_NOTCL] %s' %
@@ -1107,7 +1106,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
                 except ValueError:
                     # we may have a tuple with only one element and a comma
                     dia_plot = [float(el) for el in self.options["tooldia"].split(',') if el != ''][0]
-                self.plot2(dia_plot, obj=self, visible=visible, kind=kind)
+                self.plot2(tooldia=dia_plot, obj=self, visible=visible, kind=kind)
             else:
                 # multiple tools usage
                 if self.cnc_tools:
@@ -1117,12 +1116,16 @@ class CNCJobObject(FlatCAMObj, CNCjob):
                         self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
 
                 # TODO: until the gcode parsed will be stored on each Excellon tool this will not get executed
-                if self.exc_cnc_tools:
-                    for tooldia_key in self.exc_cnc_tools:
-                        tooldia = float('%.*f' % (self.decimals, float(tooldia_key)))
-                        # gcode_parsed = self.cnc_tools[tooldia_key]['gcode_parsed']
-                        gcode_parsed = self.gcode_parsed
-                        self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
+                # I do this so the travel lines thickness will reflect the tool diameter
+                # may work only for objects created within the app and not Gcode imported from elsewhere for which we
+                # don't know the origin
+                if self.origin_kind == "excellon":
+                    if self.exc_cnc_tools:
+                        for tooldia_key in self.exc_cnc_tools:
+                            tooldia = float('%.*f' % (self.decimals, float(tooldia_key)))
+                            # gcode_parsed = self.exc_cnc_tools[tooldia_key]['gcode_parsed']
+                            gcode_parsed = self.gcode_parsed
+                            self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
 
             self.shapes.redraw()
         except (ObjectDeleted, AttributeError):

+ 24 - 3
flatcamObjects/FlatCAMDocument.py → AppObjects/FlatCAMDocument.py

@@ -10,11 +10,11 @@
 # File modified by: Marius Stanciu                         #
 # ##########################################################
 
-from flatcamEditors.FlatCAMTextEditor import TextEditor
-from flatcamObjects.FlatCAMObj import *
+from AppEditors.FlatCAMTextEditor import TextEditor
+from AppObjects.FlatCAMObj import *
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -284,6 +284,27 @@ class DocumentObject(FlatCAMObj):
         self.ui.sel_color_entry.set_value(new_val)
         self.app.defaults['document_sel_color'] = new_val
 
+    def mirror(self, axis, point):
+        pass
+
+    def offset(self, vect):
+        pass
+
+    def rotate(self, angle, point):
+        pass
+
+    def scale(self, xfactor, yfactor=None, point=None):
+        pass
+
+    def skew(self, angle_x, angle_y, point):
+        pass
+
+    def buffer(self, distance, join, factor=None):
+        pass
+
+    def bounds(self, flatten=False):
+        return None, None, None, None
+
     def to_dict(self):
         """
         Returns a representation of the object as a dictionary.

+ 182 - 41
flatcamObjects/FlatCAMExcellon.py → AppObjects/FlatCAMExcellon.py

@@ -15,13 +15,14 @@ from shapely.geometry import Point, LineString
 
 from copy import deepcopy
 
-from flatcamParsers.ParseExcellon import Excellon
-from flatcamObjects.FlatCAMObj import *
+from AppParsers.ParseExcellon import Excellon
+from AppObjects.FlatCAMObj import *
 
 import itertools
+import numpy as np
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -31,7 +32,7 @@ if '_' not in builtins.__dict__:
 
 class ExcellonObject(FlatCAMObj, Excellon):
     """
-    Represents Excellon/Drill code.
+    Represents Excellon/Drill code. An object stored in the FlatCAM objects collection (a dict)
     """
 
     ui_type = ExcellonObjectUI
@@ -50,6 +51,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.options.update({
             "plot": True,
             "solid": False,
+            "multicolored": False,
 
             "operation": "drill",
             "milling_type": "drills",
@@ -146,9 +148,11 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
         If only one object is in exc_list parameter then this function will copy that object in the exc_final
 
-        :param exc_list: List or one object of ExcellonObject Objects to join.
-        :param exc_final: Destination ExcellonObject object.
-        :return: None
+        :param exc_list:    List or one object of ExcellonObject Objects to join.
+        :type exc_list:     list
+        :param exc_final:   Destination ExcellonObject object.
+        :type exc_final:    class
+        :return:            None
         """
 
         if decimals is None:
@@ -316,6 +320,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
         exc_final.create_geometry()
 
     def build_ui(self):
+        """
+        Will (re)build the Excellon UI updating it (the tool table)
+
+        :return:    None
+        :rtype:
+        """
         FlatCAMObj.build_ui(self)
 
         # Area Exception - exclusion shape added signal
@@ -586,9 +596,9 @@ class ExcellonObject(FlatCAMObj, Excellon):
         Configures the user interface for this object.
         Connects options to form fields.
 
-        :param ui: User interface object.
-        :type ui: ExcellonObjectUI
-        :return: None
+        :param ui:  User interface object.
+        :type ui:   ExcellonObjectUI
+        :return:    None
         """
         FlatCAMObj.set_ui(self, ui)
 
@@ -599,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,
@@ -700,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)
@@ -729,6 +742,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.ui.operation_radio.setEnabled(False)
 
     def ui_connect(self):
+        """
+        Will connect all signals in the Excellon UI that needs to be connected
+
+        :return:    None
+        :rtype:
+        """
 
         # selective plotting
         for row in range(self.ui.tools_table.rowCount() - 2):
@@ -751,6 +770,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
                 current_widget.returnPressed.connect(self.form_to_storage)
 
     def ui_disconnect(self):
+        """
+        Will disconnect all signals in the Excellon UI that needs to be disconnected
+
+        :return:    None
+        :rtype:
+        """
         # selective plotting
         for row in range(self.ui.tools_table.rowCount()):
             try:
@@ -793,6 +818,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
                     pass
 
     def on_row_selection_change(self):
+        """
+        Called when the user clicks on a row in Tools Table
+
+        :return:    None
+        :rtype:
+        """
         self.ui_disconnect()
 
         sel_rows = []
@@ -843,6 +874,14 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.ui_connect()
 
     def storage_to_form(self, dict_storage):
+        """
+        Will update the GUI with data from the "storage" in this case the dict self.tools
+
+        :param dict_storage:    A dictionary holding the data relevant for gnerating Gcode from Excellon
+        :type dict_storage:     dict
+        :return:                None
+        :rtype:
+        """
         for form_key in self.form_fields:
             for storage_key in dict_storage:
                 if form_key == storage_key and form_key not in \
@@ -854,6 +893,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
                         pass
 
     def form_to_storage(self):
+        """
+        Will update the 'storage' attribute which is the dict self.tools with data collected from GUI
+
+        :return:    None
+        :rtype:
+        """
         if self.ui.tools_table.rowCount() == 0:
             # there is no tool in tool table so we can't save the GUI elements values to storage
             return
@@ -882,6 +927,14 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.ui_connect()
 
     def on_operation_type(self, val):
+        """
+        Called by a RadioSet activated_custom signal
+
+        :param val:     Parameter passes by the signal that called this method
+        :type val:      str
+        :return:        None
+        :rtype:
+        """
         if val == 'mill':
             self.ui.mill_type_label.show()
             self.ui.milling_type_radio.show()
@@ -910,10 +963,10 @@ class ExcellonObject(FlatCAMObj, Excellon):
     def get_selected_tools_list(self):
         """
         Returns the keys to the self.tools dictionary corresponding
-        to the selections on the tool list in the GUI.
+        to the selections on the tool list in the AppGUI.
 
-        :return: List of tools.
-        :rtype: list
+        :return:    List of tools.
+        :rtype:     list
         """
 
         return [str(x.text()) for x in self.ui.tools_table.selectedItems()]
@@ -922,8 +975,8 @@ class ExcellonObject(FlatCAMObj, Excellon):
         """
         Returns a list of lists, each list in the list is made out of row elements
 
-        :return: List of table_tools items.
-        :rtype: list
+        :return:    List of table_tools items.
+        :rtype:     list
         """
         table_tools_items = []
         for x in self.ui.tools_table.selectedItems():
@@ -951,7 +1004,21 @@ class ExcellonObject(FlatCAMObj, Excellon):
     def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1, slot_type='routing'):
         """
         Returns two values, first is a boolean , if 1 then the file has slots and second contain the Excellon code
-        :return: has_slots and Excellon_code
+
+        :param whole:       Integer part digits
+        :type whole:        int
+        :param fract:       Fractional part digits
+        :type fract:        int
+        :param e_zeros:     Excellon zeros suppression: LZ or TZ
+        :type e_zeros:      str
+        :param form:        Excellon format: 'dec',
+        :type form:         str
+        :param factor:      Conversion factor
+        :type factor:       float
+        :param slot_type:   How to treat slots: "routing" or "drilling"
+        :type slot_type:    str
+        :return:            A tuple: (has_slots, Excellon_code) -> (bool, str)
+        :rtype:             tuple
         """
 
         excellon_code = ''
@@ -1118,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
@@ -1167,6 +1246,15 @@ class ExcellonObject(FlatCAMObj, Excellon):
                 return False, "Error: Milling tool is larger than hole."
 
         def geo_init(geo_obj, app_obj):
+            """
+
+            :param geo_obj:     New object
+            :type geo_obj:      GeometryObject
+            :param app_obj:     App
+            :type app_obj:      FlatCAMApp.App
+            :return:
+            :rtype:
+            """
             assert geo_obj.kind == 'geometry', "Initializer expected a GeometryObject, got %s" % type(geo_obj)
 
             # ## Add properties to the object
@@ -1179,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
@@ -1196,8 +1284,8 @@ class ExcellonObject(FlatCAMObj, Excellon):
                             Point(hole['point']).buffer(buffer_value).exterior)
 
         if use_thread:
-            def geo_thread(app_obj):
-                app_obj.new_object("geometry", outname, geo_init, plot=plot)
+            def geo_thread(a_obj):
+                a_obj.app_obj.new_object("geometry", outname, geo_init, plot=plot)
 
             # Create a promise with the new name
             self.app.collection.promise(outname)
@@ -1205,19 +1293,31 @@ class ExcellonObject(FlatCAMObj, Excellon):
             # Send to worker
             self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
         else:
-            self.app.new_object("geometry", outname, geo_init, plot=plot)
+            self.app.app_obj.new_object("geometry", outname, geo_init, plot=plot)
 
         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
@@ -1258,7 +1358,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
                 return False, "Error: Milling tool is larger than hole."
 
         def geo_init(geo_obj, app_obj):
-            assert geo_obj.kind == 'geometry' "Initializer expected a GeometryObject, got %s" % type(geo_obj)
+            assert geo_obj.kind == 'geometry', "Initializer expected a GeometryObject, got %s" % type(geo_obj)
 
             # ## Add properties to the object
 
@@ -1270,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
@@ -1300,8 +1400,8 @@ class ExcellonObject(FlatCAMObj, Excellon):
                         geo_obj.solid_geometry.append(poly)
 
         if use_thread:
-            def geo_thread(app_obj):
-                app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
+            def geo_thread(a_obj):
+                a_obj.app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
 
             # Create a promise with the new name
             self.app.collection.promise(outname)
@@ -1309,7 +1409,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
             # Send to worker
             self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
         else:
-            self.app.new_object("geometry", outname + '_slot', geo_init, plot=plot)
+            self.app.app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
 
         return True, ""
 
@@ -1317,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()
@@ -1443,7 +1543,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
         job_name = self.options["name"] + "_cnc"
         pp_excellon_name = self.options["ppname_e"]
 
-        # Object initialization function for app.new_object()
+        # Object initialization function for app.app_obj.new_object()
         def job_init(job_obj, app_obj):
             assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
 
@@ -1506,9 +1606,9 @@ class ExcellonObject(FlatCAMObj, Excellon):
             job_obj.create_geometry()
 
         # To be run in separate thread
-        def job_thread(app_obj):
+        def job_thread(a_obj):
             with self.app.proc_container.new(_("Generating CNC Code")):
-                app_obj.new_object("cncjob", job_name, job_init)
+                a_obj.app_obj.new_object("cncjob", job_name, job_init)
 
         # Create promise for the new name.
         self.app.collection.promise(job_name)
@@ -1656,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
@@ -1729,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"]:
@@ -1760,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)

+ 104 - 59
flatcamObjects/FlatCAMGeometry.py → AppObjects/FlatCAMGeometry.py

@@ -15,7 +15,7 @@ import shapely.affinity as affinity
 
 from camlib import Geometry
 
-from flatcamObjects.FlatCAMObj import *
+from AppObjects.FlatCAMObj import *
 
 import ezdxf
 import math
@@ -24,7 +24,7 @@ from copy import deepcopy
 import traceback
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -52,6 +52,7 @@ class GeometryObject(FlatCAMObj, Geometry):
 
         self.options.update({
             "plot": True,
+            "multicolored": False,
             "cutz": -0.002,
             "vtipdia": 0.1,
             "vtipangle": 30,
@@ -304,25 +305,9 @@ class GeometryObject(FlatCAMObj, Geometry):
 
         self.set_tool_offset_visibility(selected_row)
 
-        # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
-        # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
-        self.ui.name_entry.set_value(self.options['name'])
-        self.ui_connect()
-
-        self.ui.e_cut_entry.setDisabled(False) if self.ui.extracut_cb.get_value() else \
-            self.ui.e_cut_entry.setDisabled(True)
-
-        # set the text on tool_data_label after loading the object
-        sel_rows = []
-        sel_items = self.ui.geo_tools_table.selectedItems()
-        for it in sel_items:
-            sel_rows.append(it.row())
-        if len(sel_rows) > 1:
-            self.ui.tool_data_label.setText(
-                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
-            )
-
+        # -----------------------------
         # Build Exclusion Areas section
+        # -----------------------------
         e_len = len(self.app.exc_areas.exclusion_areas_storage)
         self.ui.exclusion_table.setRowCount(e_len)
 
@@ -374,6 +359,27 @@ class GeometryObject(FlatCAMObj, Geometry):
         self.ui.exclusion_table.setMinimumHeight(self.ui.exclusion_table.getHeight())
         self.ui.exclusion_table.setMaximumHeight(self.ui.exclusion_table.getHeight())
 
+        # End Build Exclusion Areas
+        # -----------------------------
+
+        # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
+        # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
+        self.ui.name_entry.set_value(self.options['name'])
+        self.ui_connect()
+
+        self.ui.e_cut_entry.setDisabled(False) if self.ui.extracut_cb.get_value() else \
+            self.ui.e_cut_entry.setDisabled(True)
+
+        # set the text on tool_data_label after loading the object
+        sel_rows = []
+        sel_items = self.ui.geo_tools_table.selectedItems()
+        for it in sel_items:
+            sel_rows.append(it.row())
+        if len(sel_rows) > 1:
+            self.ui.tool_data_label.setText(
+                "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
+            )
+
     def set_ui(self, ui):
         FlatCAMObj.set_ui(self, ui)
 
@@ -391,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,
@@ -586,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))
@@ -652,8 +661,7 @@ class GeometryObject(FlatCAMObj, Geometry):
                                 self.ui.tool_offset_entry.get_value().replace(',', '.')
                             )
                         except ValueError:
-                            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                                 _("Wrong value format entered, use a number."))
+                            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
                             return
 
     def ui_connect(self):
@@ -661,6 +669,7 @@ class GeometryObject(FlatCAMObj, Geometry):
         # changes in geometry UI
         for i in self.param_fields:
             current_widget = self.param_fields[i]
+
             if isinstance(current_widget, FCCheckBox):
                 current_widget.stateChanged.connect(self.gui_form_to_storage)
             elif isinstance(current_widget, FCComboBox):
@@ -931,7 +940,7 @@ class GeometryObject(FlatCAMObj, Geometry):
         self.ui_connect()
         self.build_ui()
 
-        # if there is no tool left in the Tools Table, enable the parameters GUI
+        # if there is no tool left in the Tools Table, enable the parameters AppGUI
         if self.ui.geo_tools_table.rowCount() != 0:
             self.ui.geo_param_frame.setDisabled(False)
 
@@ -1007,7 +1016,7 @@ class GeometryObject(FlatCAMObj, Geometry):
         self.ui_connect()
         self.build_ui()
 
-        # if there is no tool left in the Tools Table, enable the parameters GUI
+        # if there is no tool left in the Tools Table, enable the parameters AppGUI
         if self.ui.geo_tools_table.rowCount() != 0:
             self.ui.geo_param_frame.setDisabled(False)
 
@@ -1188,7 +1197,7 @@ class GeometryObject(FlatCAMObj, Geometry):
                 obj_active.options['xmax'] = 0
                 obj_active.options['ymax'] = 0
 
-        # if there is no tool left in the Tools Table, disable the parameters GUI
+        # if there is no tool left in the Tools Table, disable the parameters AppGUI
         if self.ui.geo_tools_table.rowCount() == 0:
             self.ui.geo_param_frame.setDisabled(True)
 
@@ -1382,16 +1391,18 @@ class GeometryObject(FlatCAMObj, Geometry):
         self.ui_connect()
 
     def gui_form_to_storage(self):
+        self.ui_disconnect()
+
         if self.ui.geo_tools_table.rowCount() == 0:
             # there is no tool in tool table so we can't save the GUI elements values to storage
             log.debug("GeometryObject.gui_form_to_storage() --> no tool in Tools Table, aborting.")
             return
 
-        self.ui_disconnect()
         widget_changed = self.sender()
         try:
             widget_idx = self.ui.grid3.indexOf(widget_changed)
-        except Exception:
+        except Exception as e:
+            log.debug("GeometryObject.gui_form_to_storage() -- wdg index -> %s" % str(e))
             return
 
         # those are the indexes for the V-Tip Dia and V-Tip Angle, if edited calculate the new Cut Z
@@ -1748,7 +1759,7 @@ class GeometryObject(FlatCAMObj, Geometry):
         :param tools_dict:      a dictionary that holds the whole data needed to create the Gcode
                                 (including the solid_geometry)
         :param tools_in_use:    the tools that are used, needed by some preprocessors
-        :type  tools_in_use     list of lists, each list in the list is made out of row elements of tools table from GUI
+        :type  tools_in_use     list of lists, each list in the list is made out of row elements of tools table from AppGUI
         :param segx:            number of segments on the X axis, for auto-levelling
         :param segy:            number of segments on the Y axis, for auto-levelling
         :param plot:            if True the generated object will be plotted; if False will not be plotted
@@ -1778,7 +1789,7 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.app.inform.emit(msg)
             return
 
-        # Object initialization function for app.new_object()
+        # Object initialization function for app.app_obj.new_object()
         # RUNNING ON SEPARATE THREAD!
         def job_init_single_geometry(job_obj, app_obj):
             log.debug("Creating a CNCJob out of a single-geometry")
@@ -1918,7 +1929,7 @@ class GeometryObject(FlatCAMObj, Geometry):
                 })
                 dia_cnc_dict.clear()
 
-        # Object initialization function for app.new_object()
+        # Object initialization function for app.app_obj.new_object()
         # RUNNING ON SEPARATE THREAD!
         def job_init_multi_geometry(job_obj, app_obj):
             log.debug("Creating a CNCJob out of a multi-geometry")
@@ -2072,15 +2083,15 @@ class GeometryObject(FlatCAMObj, Geometry):
 
         if use_thread:
             # To be run in separate thread
-            def job_thread(app_obj):
+            def job_thread(a_obj):
                 if self.multigeo is False:
                     with self.app.proc_container.new(_("Generating CNC Code")):
-                        if app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
-                            app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
+                        if a_obj.app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
+                            a_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
                 else:
                     with self.app.proc_container.new(_("Generating CNC Code")):
-                        if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
-                            app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
+                        if a_obj.app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
+                            a_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
 
             # Create a promise with the name
             self.app.collection.promise(outname)
@@ -2088,23 +2099,15 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
         else:
             if self.solid_geometry:
-                self.app.new_object("cncjob", outname, job_init_single_geometry, plot=plot)
+                self.app.app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot)
             else:
-                self.app.new_object("cncjob", outname, job_init_multi_geometry, plot=plot)
-
-    def generatecncjob(
-            self, outname=None,
-            dia=None, offset=None,
-            z_cut=None, z_move=None,
-            feedrate=None, feedrate_z=None, feedrate_rapid=None,
-            spindlespeed=None, dwell=None, dwelltime=None,
-            multidepth=None, depthperpass=None,
-            toolchange=None, toolchangez=None, toolchangexy=None,
-            extracut=None, extracut_length=None, startz=None, endz=None,
-            pp=None,
-            segx=None, segy=None,
-            use_thread=True,
-            plot=True):
+                self.app.app_obj.new_object("cncjob", outname, job_init_multi_geometry, plot=plot)
+
+    def generatecncjob(self, outname=None, dia=None, offset=None, z_cut=None, z_move=None,
+            feedrate=None, feedrate_z=None, feedrate_rapid=None, spindlespeed=None, dwell=None, dwelltime=None,
+            multidepth=None, dpp=None, toolchange=None, toolchangez=None, toolchangexy=None,
+            extracut=None, extracut_length=None, startz=None, endz=None, endxy=None, pp=None, segx=None, segy=None,
+            use_thread=True, plot=True):
         """
         Only used by the TCL Command Cncjob.
         Creates a CNCJob out of this Geometry object. The actual
@@ -2123,14 +2126,17 @@ class GeometryObject(FlatCAMObj, Geometry):
         :param dwell:
         :param dwelltime:
         :param multidepth:
-        :param depthperpass:
+        :param dpp:             Depth for each pass when multidepth parameter is True
         :param toolchange:
         :param toolchangez:
-        :param toolchangexy:
+        :param toolchangexy:    A sequence ox X,Y coordinates: a 2-length tuple or a string.
+                                Coordinates in X,Y plane for the Toolchange event
         :param extracut:
         :param extracut_length:
         :param startz:
         :param endz:
+        :param endxy:           A sequence ox X,Y coordinates: a 2-length tuple or a string.
+                                Coordinates in X, Y plane for the last move after ending the job.
         :param pp:              Name of the preprocessor
         :param segx:
         :param segy:
@@ -2150,7 +2156,7 @@ class GeometryObject(FlatCAMObj, Geometry):
         feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"])
 
         multidepth = multidepth if multidepth is not None else self.options["multidepth"]
-        depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"])
+        depthperpass = dpp if dpp is not None else float(self.options["depthperpass"])
 
         segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
         segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
@@ -2160,10 +2166,21 @@ class GeometryObject(FlatCAMObj, Geometry):
 
         startz = startz if startz is not None else self.options["startz"]
         endz = endz if endz is not None else float(self.options["endz"])
-        endxy = self.options["endxy"]
+
+        endxy = endxy if endxy else self.options["endxy"]
+        if isinstance(endxy, str):
+            endxy = re.sub('[()\[\]]', '', endxy)
+            if endxy and endxy != '':
+                endxy = [float(eval(a)) for a in endxy.split(",")]
 
         toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"])
+
         toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
+        if isinstance(toolchangexy, str):
+            toolchangexy = re.sub('[()\[\]]', '', toolchangexy)
+            if toolchangexy and toolchangexy != '':
+                toolchangexy = [float(eval(a)) for a in toolchangexy.split(",")]
+
         toolchange = toolchange if toolchange else self.options["toolchange"]
 
         offset = offset if offset else 0.0
@@ -2175,7 +2192,7 @@ class GeometryObject(FlatCAMObj, Geometry):
 
         ppname_g = pp if pp else self.options["ppname_g"]
 
-        # Object initialization function for app.new_object()
+        # Object initialization function for app.app_obj.new_object()
         # RUNNING ON SEPARATE THREAD!
         def job_init(job_obj, app_obj):
             assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
@@ -2224,7 +2241,7 @@ class GeometryObject(FlatCAMObj, Geometry):
             # To be run in separate thread
             def job_thread(app_obj):
                 with self.app.proc_container.new(_("Generating CNC Code")):
-                    app_obj.new_object("cncjob", outname, job_init, plot=plot)
+                    app_obj.app_obj.new_object("cncjob", outname, job_init, plot=plot)
                     app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname)
 
             # Create a promise with the name
@@ -2232,7 +2249,7 @@ class GeometryObject(FlatCAMObj, Geometry):
             # Send to worker
             self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
         else:
-            self.app.new_object("cncjob", outname, job_init, plot=plot)
+            self.app.app_obj.new_object("cncjob", outname, job_init, plot=plot)
 
     # def on_plot_cb_click(self, *args):
     #     if self.muted_ui:
@@ -2652,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
@@ -2659,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
@@ -2727,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):
         """

+ 66 - 434
flatcamObjects/FlatCAMGerber.py → AppObjects/FlatCAMGerber.py

@@ -14,15 +14,15 @@
 from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
 from shapely.ops import cascaded_union
 
-from flatcamParsers.ParseGerber import Gerber
-from flatcamObjects.FlatCAMObj import *
+from AppParsers.ParseGerber import Gerber
+from AppObjects.FlatCAMObj import *
 
 import math
 import numpy as np
 from copy import deepcopy
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -115,23 +115,13 @@ 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'
+            "milling_type": 'cl',
         })
 
         # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
@@ -197,88 +187,44 @@ 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)
         self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
         self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run)
+
         self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
         self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
         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()
@@ -296,58 +242,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)
 
@@ -530,7 +424,7 @@ class GerberObject(FlatCAMObj, Gerber):
                 return "fail"
             geo_obj.solid_geometry = non_copper
 
-        self.app.new_object("geometry", name, geo_init)
+        self.app.app_obj.new_object("geometry", name, geo_init)
 
     def on_generatebb_button_click(self, *args):
         self.app.defaults.report_usage("gerber_on_generatebb_button")
@@ -556,244 +450,24 @@ class GerberObject(FlatCAMObj, Gerber):
                 return "fail"
             geo_obj.solid_geometry = bounding_box
 
-        self.app.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.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."))
+        self.app.app_obj.new_object("geometry", name, geo_init)
 
     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 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
+        :param plot: Boolean:   if to plot the resulting geometry object
+        :return:                None
         """
 
         if geometry is None:
@@ -802,20 +476,20 @@ class GerberObject(FlatCAMObj, Gerber):
             work_geo = geometry
 
         if dia is None:
-            dia = float(self.options["isotooldia"])
+            dia = float(self.app.defaults["tools_iso_tooldia"])
 
         if passes is None:
-            passes = int(self.options["isopasses"])
+            passes = int(self.app.defaults["tools_iso_passes"])
 
         if overlap is None:
-            overlap = float(self.options["isooverlap"])
+            overlap = float(self.app.defaults["tools_iso_overlap"])
 
         overlap /= 100.0
 
-        combine = self.options["combine_passes"] if combine is None else bool(combine)
+        combine = self.app.defaults["tools_iso_combine_passes"] if combine is None else bool(combine)
 
         if milling_type is None:
-            milling_type = self.options["milling_type"]
+            milling_type = self.app.defaults["tools_iso_milling_type"]
 
         if iso_type is None:
             iso_t = 2
@@ -837,16 +511,16 @@ class GerberObject(FlatCAMObj, Gerber):
 
             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.options["cnctooldia"] = str(dia)
+                geo_obj.tool_type = self.app.defaults["tools_iso_tool_type"]
 
                 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()
+                if geo_obj.tool_type.lower() == 'v':
+                    new_cutz = self.app.defaults["tools_iso_tool_cutz"]
+                    new_vtipdia = self.app.defaults["tools_iso_tool_vtipdia"]
+                    new_vtipangle = self.app.defaults["tools_iso_tool_vtipangle"]
                     tool_type = 'V'
                 else:
                     new_cutz = self.app.defaults['geometry_cutz']
@@ -885,7 +559,7 @@ class GerberObject(FlatCAMObj, Gerber):
                 geo_obj.tools['1'] = {}
                 geo_obj.tools.update({
                     '1': {
-                        'tooldia': float(self.options["isotooldia"]),
+                        'tooldia': dia,
                         'offset': 'Path',
                         'offset_value': 0.0,
                         'type': _('Rough'),
@@ -937,12 +611,11 @@ class GerberObject(FlatCAMObj, Gerber):
                 # ############################################################
                 # ########## AREA SUBTRACTION ################################
                 # ############################################################
-                if self.ui.except_cb.get_value():
-                    self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                    geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
+                # if self.app.defaults["tools_iso_except"]:
+                #     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.new_object("geometry", iso_name, iso_init, plot=plot)
+            self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
         else:
             for i in range(passes):
 
@@ -970,17 +643,13 @@ class GerberObject(FlatCAMObj, Gerber):
 
                 def iso_init(geo_obj, app_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'
+                    geo_obj.options["cnctooldia"] = str(dia)
+                    geo_obj.tool_type = self.app.defaults["tools_iso_tool_type"]
 
                     # 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)
+                                                  follow=follow, nr_passes=i)
 
                     if geom == 'fail':
                         app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
@@ -991,10 +660,10 @@ class GerberObject(FlatCAMObj, Gerber):
                     # 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()
+                    if self.app.defaults["tools_iso_tool_type"].lower() == 'v':
+                        new_cutz = self.app.defaults["tools_iso_tool_cutz"]
+                        new_vtipdia = self.app.defaults["tools_iso_tool_vtipdia"]
+                        new_vtipangle = self.app.defaults["tools_iso_tool_vtipangle"]
                         tool_type = 'V'
                     else:
                         new_cutz = self.app.defaults['geometry_cutz']
@@ -1033,7 +702,7 @@ class GerberObject(FlatCAMObj, Gerber):
                     geo_obj.tools['1'] = {}
                     geo_obj.tools.update({
                         '1': {
-                            'tooldia': float(self.options["isotooldia"]),
+                            'tooldia': dia,
                             'offset': 'Path',
                             'offset_value': 0.0,
                             'type': _('Rough'),
@@ -1067,12 +736,11 @@ class GerberObject(FlatCAMObj, Gerber):
                     # ############################################################
                     # ########## AREA SUBTRACTION ################################
                     # ############################################################
-                    if self.ui.except_cb.get_value():
-                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                        geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
+                    # if self.app.defaults["tools_iso_except"]:
+                    #     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.new_object("geometry", iso_name, iso_init, plot=plot)
+                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
@@ -1114,64 +782,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.app.defaults["tools_iso_tooldia"])
+            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:

+ 29 - 16
flatcamObjects/FlatCAMObj.py → AppObjects/FlatCAMObj.py

@@ -12,15 +12,15 @@
 
 import inspect  # TODO: For debugging only.
 
-from flatcamGUI.ObjectUI import *
+from AppGUI.ObjectUI import *
 
-from FlatCAMCommon import LoudDict
-from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+from Common import LoudDict
+from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
 
 import sys
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -43,7 +43,7 @@ class ValidationError(Exception):
 class FlatCAMObj(QtCore.QObject):
     """
     Base type of objects handled in FlatCAM. These become interactive
-    in the GUI, can be plotted, and their options can be modified
+    in the AppGUI, can be plotted, and their options can be modified
     by the user in their respective forms.
     """
 
@@ -139,7 +139,7 @@ class FlatCAMObj(QtCore.QObject):
                 except KeyError:
                     log.debug("FlatCAMObj.from_dict() --> KeyError: %s. "
                               "Means that we are loading an old project that don't"
-                              "have all attributes in the latest FlatCAM." % str(attr))
+                              "have all attributes in the latest application version." % str(attr))
                     pass
 
     def on_options_change(self, key):
@@ -182,6 +182,11 @@ class FlatCAMObj(QtCore.QObject):
         except (TypeError, AttributeError):
             pass
 
+        try:
+            self.ui.transformations_button.clicked.connect(self.app.transform_tool.run)
+        except (TypeError, AttributeError):
+            pass
+
         # self.ui.skew_button.clicked.connect(self.on_skew_button_click)
 
     def build_ui(self):
@@ -245,14 +250,14 @@ class FlatCAMObj(QtCore.QObject):
             self.app.proc_container.update_view_text('')
             with self.app.proc_container.new('%s...' % _("Plotting")):
                 self.plot()
-            self.app.object_changed.emit(self)
+            self.app.app_obj.object_changed.emit(self)
 
         self.app.worker_task.emit({'fcn': worker_task, 'params': []})
 
     def on_scale_button_click(self):
         self.read_form()
         try:
-            factor = float(eval(self.ui.scale_entry.get_value()))
+            factor = float(self.ui.scale_entry.get_value())
         except Exception as e:
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
             log.debug("FlatCAMObj.on_scale_button_click() -- %s" % str(e))
@@ -276,7 +281,7 @@ class FlatCAMObj(QtCore.QObject):
             self.app.proc_container.update_view_text('')
             with self.app.proc_container.new('%s...' % _("Plotting")):
                 self.plot()
-            self.app.object_changed.emit(self)
+            self.app.app_obj.object_changed.emit(self)
 
         self.app.worker_task.emit({'fcn': worker_task, 'params': []})
 
@@ -292,7 +297,7 @@ class FlatCAMObj(QtCore.QObject):
             self.app.proc_container.update_view_text('')
             with self.app.proc_container.new('%s...' % _("Plotting")):
                 self.plot()
-            self.app.object_changed.emit(self)
+            self.app.app_obj.object_changed.emit(self)
 
         self.app.worker_task.emit({'fcn': worker_task, 'params': []})
 
@@ -372,7 +377,7 @@ class FlatCAMObj(QtCore.QObject):
         def plot_task():
             with self.app.proc_container.new('%s...' % _("Plotting")):
                 self.plot()
-            self.app.object_changed.emit(self)
+            self.app.app_obj.object_changed.emit(self)
 
         self.app.worker_task.emit({'fcn': plot_task, 'params': []})
 
@@ -459,8 +464,16 @@ class FlatCAMObj(QtCore.QObject):
     def visible(self, value, threaded=True):
         log.debug("FlatCAMObj.visible()")
 
-        def worker_task(app_obj):
-            self.shapes.visible = value
+        current_visibility = self.shapes.visible
+        # self.shapes.visible = value   # maybe this is slower in VisPy? use enabled property?
+
+        def task(current_visibility):
+            if current_visibility is True:
+                if value is False:
+                    self.shapes.visible = False
+            else:
+                if value is True:
+                    self.shapes.visible = True
 
             if self.app.is_legacy is False:
                 # Not all object types has annotations
@@ -469,10 +482,10 @@ class FlatCAMObj(QtCore.QObject):
                 except Exception:
                     pass
 
-        if threaded is False:
-            worker_task(app_obj=self.app)
+        if threaded:
+            self.app.worker_task.emit({'fcn': task, 'params': [current_visibility]})
         else:
-            self.app.worker_task.emit({'fcn': worker_task, 'params': [self]})
+            task(current_visibility)
 
     @property
     def drawing_tolerance(self):

+ 33 - 7
flatcamObjects/FlatCAMScript.py → AppObjects/FlatCAMScript.py

@@ -10,16 +10,16 @@
 # File modified by: Marius Stanciu                         #
 # ##########################################################
 
-from flatcamEditors.FlatCAMTextEditor import TextEditor
-from flatcamObjects.FlatCAMObj import *
-from flatcamGUI.ObjectUI import *
+from AppEditors.FlatCAMTextEditor import TextEditor
+from AppObjects.FlatCAMObj import *
+from AppGUI.ObjectUI import *
 
 import tkinter as tk
 import sys
 from copy import deepcopy
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -183,7 +183,13 @@ class ScriptObject(FlatCAMObj):
         if self.app.ui.shell_dock.isHidden():
             self.app.ui.shell_dock.show()
 
-        self.script_code = deepcopy(self.script_editor_tab.code_editor.toPlainText())
+        self.app.shell.open_processing()  # Disables input box.
+
+        # make sure that the pixmaps are not updated when running this as they will crash
+        # TODO find why the pixmaps load crash when run from this object (perhaps another thread?)
+        self.app.ui.fcinfo.lock_pmaps = True
+
+        self.script_code = self.script_editor_tab.code_editor.toPlainText()
 
         old_line = ''
         for tcl_command_line in self.script_code.splitlines():
@@ -202,8 +208,6 @@ class ScriptObject(FlatCAMObj):
 
                 # execute the actual Tcl command
                 try:
-                    self.app.shell.open_processing()  # Disables input box.
-
                     result = self.app.shell.tcl.eval(str(new_command))
                     if result != 'None':
                         self.app.shell.append_output(result + '\n')
@@ -220,6 +224,7 @@ class ScriptObject(FlatCAMObj):
             log.error("Exec command Exception: %s\n" % result)
             self.app.shell.append_error('ERROR: %s\n '% result)
 
+        self.app.ui.fcinfo.lock_pmaps = False
         self.app.shell.close_processing()
 
     def on_autocomplete_changed(self, state):
@@ -228,6 +233,27 @@ class ScriptObject(FlatCAMObj):
         else:
             self.script_editor_tab.code_editor.completer_enable = False
 
+    def mirror(self, axis, point):
+        pass
+
+    def offset(self, vect):
+        pass
+
+    def rotate(self, angle, point):
+        pass
+
+    def scale(self, xfactor, yfactor=None, point=None):
+        pass
+
+    def skew(self, angle_x, angle_y, point):
+        pass
+
+    def buffer(self, distance, join, factor=None):
+        pass
+
+    def bounds(self, flatten=False):
+        return None, None, None, None
+
     def to_dict(self):
         """
         Returns a representation of the object as a dictionary.

+ 216 - 16
flatcamObjects/ObjectCollection.py → AppObjects/ObjectCollection.py

@@ -16,13 +16,13 @@ from PyQt5.QtCore import Qt, QSettings
 from PyQt5.QtGui import QColor
 # from PyQt5.QtCore import QModelIndex
 
-from flatcamObjects.FlatCAMObj import FlatCAMObj
-from flatcamObjects.FlatCAMCNCJob import CNCJobObject
-from flatcamObjects.FlatCAMDocument import DocumentObject
-from flatcamObjects.FlatCAMExcellon import ExcellonObject
-from flatcamObjects.FlatCAMGeometry import GeometryObject
-from flatcamObjects.FlatCAMGerber import GerberObject
-from flatcamObjects.FlatCAMScript import ScriptObject
+from AppObjects.FlatCAMObj import FlatCAMObj
+from AppObjects.FlatCAMCNCJob import CNCJobObject
+from AppObjects.FlatCAMDocument import DocumentObject
+from AppObjects.FlatCAMExcellon import ExcellonObject
+from AppObjects.FlatCAMGeometry import GeometryObject
+from AppObjects.FlatCAMGerber import GerberObject
+from AppObjects.FlatCAMScript import ScriptObject
 
 import inspect  # TODO: Remove
 
@@ -32,7 +32,7 @@ from copy import deepcopy
 from numpy import Inf
 
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -50,12 +50,13 @@ class KeySensitiveListView(QtWidgets.QTreeView):
     def __init__(self, app, parent=None):
         super(KeySensitiveListView, self).__init__(parent)
         self.setHeaderHidden(True)
-        self.setEditTriggers(QtWidgets.QTreeView.SelectedClicked)
+        # self.setEditTriggers(QtWidgets.QTreeView.SelectedClicked)     # allow Edit on Tree
+        self.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)
 
         # self.setRootIsDecorated(False)
         # self.setExpandsOnDoubleClick(False)
 
-        # Enable dragging and dropping onto the GUI
+        # Enable dragging and dropping onto the AppGUI
         self.setAcceptDrops(True)
         self.filename = ""
         self.app = app
@@ -334,6 +335,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         self.click_modifier = None
 
         self.update_list_signal.connect(self.on_update_list_signal)
+        self.view.activated.connect(self.on_row_activated)
+        self.item_selected.connect(self.on_row_selected)
 
     def promise(self, obj_name):
         log.debug("Object %s has been promised." % obj_name)
@@ -382,8 +385,9 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
                 if type(obj) != GeometryObject:
                     self.app.ui.menuprojectgeneratecnc.setVisible(False)
-                if type(obj) != GeometryObject and type(obj) != ExcellonObject and type(obj) != GerberObject:
-                    self.app.ui.menuprojectedit.setVisible(False)
+                # if type(obj) != GeometryObject and type(obj) != ExcellonObject and type(obj) != GerberObject or \
+                #         type(obj) != CNCJobObject:
+                #     self.app.ui.menuprojectedit.setVisible(False)
                 if type(obj) != GerberObject and type(obj) != ExcellonObject and type(obj) != CNCJobObject:
                     self.app.ui.menuprojectviewsource.setVisible(False)
                 if type(obj) != GerberObject and type(obj) != GeometryObject and type(obj) != ExcellonObject and \
@@ -550,7 +554,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         while name in self.get_names():
             # ## Create a new name
             # Ends with number?
-            log.debug("new_object(): Object name (%s) exists, changing." % name)
+            log.debug("app_obj.new_object(): Object name (%s) exists, changing." % name)
             match = re.search(r'(.*[^\d])?(\d+)$', name)
             if match:  # Yes: Increment the number!
                 base = match.group(1) or ''
@@ -596,8 +600,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         """
         Gets a list of the names of all objects in the collection.
 
-        :return: List of names.
-        :rtype: list
+        :return:            List of names.
+        :rtype:             list
         """
 
         # log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
@@ -985,7 +989,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
     def get_list(self):
         """
-        Will return a list of all objects currently opened.
+        Will return a list of all objects currently opened. Except FlatCAMScript and FlatCAMDocuments
 
         :return:
         """
@@ -998,3 +1002,199 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
     def update_view(self):
         self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
+
+    def on_row_activated(self, index):
+        if index.isValid():
+            if index.internalPointer().parent_item != self.root_item:
+                self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+        self.on_item_activated(index)
+
+    def on_row_selected(self, obj_name):
+        """
+        This is a special string; when received it will make all Menu -> Objects entries unchecked
+        It mean we clicked outside of the items and deselected all
+
+        :param obj_name:
+        :return:
+        """
+        if obj_name == 'none':
+            for act in self.app.ui.menuobjects.actions():
+                act.setChecked(False)
+            return
+
+        # get the name of the selected objects and add them to a list
+        name_list = []
+        for obj in self.get_selected():
+            name_list.append(obj.options['name'])
+
+        # set all actions as unchecked but the ones selected make them checked
+        for act in self.app.ui.menuobjects.actions():
+            act.setChecked(False)
+            if act.text() in name_list:
+                act.setChecked(True)
+
+    def on_collection_updated(self, obj, state, old_name):
+        """
+        Create a menu from the object loaded in the collection.
+
+        :param obj:         object that was changed (added, deleted, renamed)
+        :param state:       what was done with the object. Can be: added, deleted, delete_all, renamed
+        :param old_name:    the old name of the object before the action that triggered this slot happened
+        :return:            None
+        """
+        icon_files = {
+            "gerber": self.app.resource_location + "/flatcam_icon16.png",
+            "excellon": self.app.resource_location + "/drill16.png",
+            "cncjob": self.app.resource_location + "/cnc16.png",
+            "geometry": self.app.resource_location + "/geometry16.png",
+            "script": self.app.resource_location + "/script_new16.png",
+            "document": self.app.resource_location + "/notes16_1.png"
+        }
+
+        if state == 'append':
+            for act in self.app.ui.menuobjects.actions():
+                try:
+                    act.triggered.disconnect()
+                except TypeError:
+                    pass
+            self.app.ui.menuobjects.clear()
+
+            gerber_list = []
+            exc_list = []
+            cncjob_list = []
+            geo_list = []
+            script_list = []
+            doc_list = []
+
+            for name in self.get_names():
+                obj_named = self.get_by_name(name)
+                if obj_named.kind == 'gerber':
+                    gerber_list.append(name)
+                elif obj_named.kind == 'excellon':
+                    exc_list.append(name)
+                elif obj_named.kind == 'cncjob':
+                    cncjob_list.append(name)
+                elif obj_named.kind == 'geometry':
+                    geo_list.append(name)
+                elif obj_named.kind == 'script':
+                    script_list.append(name)
+                elif obj_named.kind == 'document':
+                    doc_list.append(name)
+
+            def add_act(o_name):
+                obj_for_icon = self.get_by_name(o_name)
+                add_action = QtWidgets.QAction(parent=self.app.ui.menuobjects)
+                add_action.setCheckable(True)
+                add_action.setText(o_name)
+                add_action.setIcon(QtGui.QIcon(icon_files[obj_for_icon.kind]))
+                add_action.triggered.connect(
+                    lambda: self.set_active(o_name) if add_action.isChecked() is True else
+                    self.set_inactive(o_name))
+                self.app.ui.menuobjects.addAction(add_action)
+
+            for name in gerber_list:
+                add_act(name)
+            self.app.ui.menuobjects.addSeparator()
+
+            for name in exc_list:
+                add_act(name)
+            self.app.ui.menuobjects.addSeparator()
+
+            for name in cncjob_list:
+                add_act(name)
+            self.app.ui.menuobjects.addSeparator()
+
+            for name in geo_list:
+                add_act(name)
+            self.app.ui.menuobjects.addSeparator()
+
+            for name in script_list:
+                add_act(name)
+            self.app.ui.menuobjects.addSeparator()
+
+            for name in doc_list:
+                add_act(name)
+
+            self.app.ui.menuobjects.addSeparator()
+            self.app.ui.menuobjects_selall = self.app.ui.menuobjects.addAction(
+                QtGui.QIcon(self.app.resource_location + '/select_all.png'),
+                _('Select All')
+            )
+            self.app.ui.menuobjects_unselall = self.app.ui.menuobjects.addAction(
+                QtGui.QIcon(self.app.resource_location + '/deselect_all32.png'),
+                _('Deselect All')
+            )
+            self.app.ui.menuobjects_selall.triggered.connect(lambda: self.on_objects_selection(True))
+            self.app.ui.menuobjects_unselall.triggered.connect(lambda: self.on_objects_selection(False))
+
+        elif state == 'delete':
+            for act in self.app.ui.menuobjects.actions():
+                if act.text() == obj.options['name']:
+                    try:
+                        act.triggered.disconnect()
+                    except TypeError:
+                        pass
+                    self.app.ui.menuobjects.removeAction(act)
+                    break
+        elif state == 'rename':
+            for act in self.app.ui.menuobjects.actions():
+                if act.text() == old_name:
+                    add_action = QtWidgets.QAction(parent=self.app.ui.menuobjects)
+                    add_action.setText(obj.options['name'])
+                    add_action.setIcon(QtGui.QIcon(icon_files[obj.kind]))
+                    add_action.triggered.connect(
+                        lambda: self.set_active(obj.options['name']) if add_action.isChecked() is True else
+                        self.set_inactive(obj.options['name']))
+
+                    self.app.ui.menuobjects.insertAction(act, add_action)
+
+                    try:
+                        act.triggered.disconnect()
+                    except TypeError:
+                        pass
+                    self.app.ui.menuobjects.removeAction(act)
+                    break
+        elif state == 'delete_all':
+            for act in self.app.ui.menuobjects.actions():
+                try:
+                    act.triggered.disconnect()
+                except TypeError:
+                    pass
+            self.app.ui.menuobjects.clear()
+
+            self.app.ui.menuobjects.addSeparator()
+            self.app.ui.menuobjects_selall = self.app.ui.menuobjects.addAction(
+                QtGui.QIcon(self.app.resource_location + '/select_all.png'),
+                _('Select All')
+            )
+            self.app.ui.menuobjects_unselall = self.app.ui.menuobjects.addAction(
+                QtGui.QIcon(self.app.resource_location + '/deselect_all32.png'),
+                _('Deselect All')
+            )
+            self.app.ui.menuobjects_selall.triggered.connect(lambda: self.on_objects_selection(True))
+            self.app.ui.menuobjects_unselall.triggered.connect(lambda: self.on_objects_selection(False))
+
+    def on_objects_selection(self, on_off):
+        obj_list = self.get_names()
+
+        if on_off is True:
+            self.set_all_active()
+            for act in self.app.ui.menuobjects.actions():
+                try:
+                    act.setChecked(True)
+                except Exception:
+                    pass
+            if obj_list:
+                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():
+                try:
+                    act.setChecked(False)
+                except Exception:
+                    pass
+
+            if obj_list:
+                self.app.inform[str, bool].emit('%s' % _("Objects selection is cleared."), False)
+            else:
+                self.app.inform[str, bool].emit('', False)

+ 0 - 0
flatcamObjects/__init__.py → AppObjects/__init__.py


+ 2 - 2
flatcamParsers/ParseDXF.py → AppParsers/ParseDXF.py

@@ -12,8 +12,8 @@ import logging
 
 log = logging.getLogger('base2')
 
-from flatcamParsers.ParseFont import *
-from flatcamParsers.ParseDXF_Spline import *
+from AppParsers.ParseFont import *
+from AppParsers.ParseDXF_Spline import *
 
 
 def distance(pt1, pt2):

+ 0 - 0
flatcamParsers/ParseDXF_Spline.py → AppParsers/ParseDXF_Spline.py


+ 15 - 16
flatcamParsers/ParseExcellon.py → AppParsers/ParseExcellon.py

@@ -6,7 +6,7 @@
 # MIT Licence                                                 #
 # ########################################################## ##
 
-from camlib import Geometry
+from camlib import Geometry, grace
 
 import shapely.affinity as affinity
 from shapely.geometry import Point, LineString
@@ -17,8 +17,7 @@ import logging
 import traceback
 from copy import deepcopy
 
-import FlatCAMTranslation as fcTranslate
-from FlatCAMCommon import GracefulException as grace
+# import AppTranslation as fcTranslate
 
 import gettext
 import builtins
@@ -967,7 +966,7 @@ class Excellon(Geometry):
         :return: None
         """
 
-        log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry()")
+        log.debug("AppParsers.ParseExcellon.Excellon.create_geometry()")
         self.solid_geometry = []
         try:
             # clear the solid_geometry in self.tools
@@ -982,7 +981,7 @@ class Excellon(Geometry):
                                          _("Excellon.create_geometry() -> a drill location was skipped "
                                            "due of not having a tool associated.\n"
                                            "Check the resulting GCode."))
-                    log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry() -> a drill location was skipped "
+                    log.debug("AppParsers.ParseExcellon.Excellon.create_geometry() -> a drill location was skipped "
                               "due of not having a tool associated")
                     continue
                 tooldia = self.tools[drill['tool']]['C']
@@ -1006,7 +1005,7 @@ class Excellon(Geometry):
                 self.tools[tool_in_slots]['solid_geometry'].append(poly)
                 self.tools[tool_in_slots]['data'] = deepcopy(self.default_data)
         except Exception as e:
-            log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry() -> "
+            log.debug("AppParsers.ParseExcellon.Excellon.create_geometry() -> "
                       "Excellon geometry creation failed due of ERROR: %s" % str(e))
             return "fail"
 
@@ -1018,10 +1017,10 @@ class Excellon(Geometry):
         :param flatten:     No used
         """
 
-        log.debug("flatcamParsers.ParseExcellon.Excellon.bounds()")
+        log.debug("AppParsers.ParseExcellon.Excellon.bounds()")
 
         if self.solid_geometry is None or not self.tools:
-            log.debug("flatcamParsers.ParseExcellon.Excellon -> solid_geometry is None")
+            log.debug("AppParsers.ParseExcellon.Excellon -> solid_geometry is None")
             return 0, 0, 0, 0
 
         def bounds_rec(obj):
@@ -1069,7 +1068,7 @@ class Excellon(Geometry):
         This function first convert to the the units found in the Excellon file but it converts tools that
         are not there yet so it has no effect other than it signal that the units are the ones in the file.
 
-        On object creation, in new_object(), true conversion is done because this is done at the end of the
+        On object creation, in app_obj.new_object(), true conversion is done because this is done at the end of the
         Excellon file parsing, the tools are inside and self.tools is really converted from the units found
         inside the file to the FlatCAM units.
 
@@ -1092,7 +1091,7 @@ class Excellon(Geometry):
         else:
             log.error("Unsupported units: %s" % str(obj_units))
             factor = 1.0
-        log.debug("flatcamParsers.ParseExcellon.Excellon.convert_units() --> Factor: %s" % str(factor))
+        log.debug("AppParsers.ParseExcellon.Excellon.convert_units() --> Factor: %s" % str(factor))
 
         self.units = obj_units
         self.scale(factor, factor)
@@ -1118,7 +1117,7 @@ class Excellon(Geometry):
         :return:            None
         :rtype:             None
         """
-        log.debug("flatcamParsers.ParseExcellon.Excellon.scale()")
+        log.debug("AppParsers.ParseExcellon.Excellon.scale()")
 
         if yfactor is None:
             yfactor = xfactor
@@ -1183,7 +1182,7 @@ class Excellon(Geometry):
         :type vect: tuple
         :return: None
         """
-        log.debug("flatcamParsers.ParseExcellon.Excellon.offset()")
+        log.debug("AppParsers.ParseExcellon.Excellon.offset()")
 
         dx, dy = vect
 
@@ -1243,7 +1242,7 @@ class Excellon(Geometry):
         :type point:        list
         :return:            None
         """
-        log.debug("flatcamParsers.ParseExcellon.Excellon.mirror()")
+        log.debug("AppParsers.ParseExcellon.Excellon.mirror()")
 
         px, py = point
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
@@ -1309,7 +1308,7 @@ class Excellon(Geometry):
         See shapely manual for more information:
         http://toblerity.org/shapely/manual.html#affine-transformations
         """
-        log.debug("flatcamParsers.ParseExcellon.Excellon.skew()")
+        log.debug("AppParsers.ParseExcellon.Excellon.skew()")
 
         if angle_x is None:
             angle_x = 0.0
@@ -1396,7 +1395,7 @@ class Excellon(Geometry):
         :param point:   tuple of coordinates (x, y)
         :return:        None
         """
-        log.debug("flatcamParsers.ParseExcellon.Excellon.rotate()")
+        log.debug("AppParsers.ParseExcellon.Excellon.rotate()")
 
         if angle == 0:
             return
@@ -1479,7 +1478,7 @@ class Excellon(Geometry):
         :param join:        The type of line joint used by the shapely buffer method: round, square, bevel
         :return:            None
         """
-        log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()")
+        log.debug("AppParsers.ParseExcellon.Excellon.buffer()")
 
         if distance == 0:
             return

+ 1 - 1
flatcamParsers/ParseFont.py → AppParsers/ParseFont.py

@@ -22,7 +22,7 @@ from fontTools import ttLib
 
 import logging
 import gettext
-import FlatCAMTranslation as fcTranslate
+import AppTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov