ソースを参照

- maintenance_1

Marius 5 年 前
コミット
5abb7866d8
100 ファイル変更2550 行追加12047 行削除
  1. 0 181
      AppGUI/ColumnarFlowLayout.py
  2. 0 327
      AppGUI/preferences/OptionUI.py
  3. 0 77
      AppGUI/preferences/OptionsGroupUI.py
  4. 0 41
      AppGUI/preferences/PreferencesSectionUI.py
  5. 0 302
      AppGUI/preferences/general/GeneralAppSettingsGroupUI.py
  6. 0 100
      AppGUI/preferences/gerber/GerberOptPrefGroupUI.py
  7. 0 81
      AppGUI/preferences/tools/ToolsCornersPrefGroupUI.py
  8. 0 320
      AppGUI/preferences/tools/ToolsISOPrefGroupUI.py
  9. 0 394
      AppObjects/AppObject.py
  10. 0 440
      AppTools/ToolCorners.py
  11. 0 455
      AppTools/ToolEtchCompensation.py
  12. 0 3018
      AppTools/ToolIsolation.py
  13. 0 361
      AppTools/ToolPDF.py
  14. 0 45
      AppTools/__init__.py
  15. 6 229
      CHANGELOG.md
  16. 2 2
      FlatCAM.py
  17. 628 173
      FlatCAMApp.py
  18. 6 6
      FlatCAMBookmark.py
  19. 63 344
      FlatCAMCommon.py
  20. 18 224
      FlatCAMDB.py
  21. 0 0
      FlatCAMPool.py
  22. 4 4
      FlatCAMPostProc.py
  23. 2 2
      FlatCAMProcess.py
  24. 19 22
      FlatCAMTool.py
  25. 0 4
      FlatCAMTranslation.py
  26. 0 0
      FlatCAMWorker.py
  27. 1 1
      FlatCAMWorkerStack.py
  28. 0 195
      Utils/vispy_example.py
  29. BIN
      assets/resources/axis16.png
  30. BIN
      assets/resources/clear_line16.png
  31. BIN
      assets/resources/contribute256.png
  32. BIN
      assets/resources/corners_32.png
  33. BIN
      assets/resources/dark_resources/axis16.png
  34. BIN
      assets/resources/dark_resources/clear_line16.png
  35. BIN
      assets/resources/dark_resources/contribute256.png
  36. BIN
      assets/resources/dark_resources/corners_32.png
  37. BIN
      assets/resources/dark_resources/edit_file16.png
  38. BIN
      assets/resources/dark_resources/edit_file32.png
  39. BIN
      assets/resources/dark_resources/etch_32.png
  40. BIN
      assets/resources/dark_resources/hud16.png
  41. BIN
      assets/resources/dark_resources/hud_32.png
  42. BIN
      assets/resources/dark_resources/iso_16.png
  43. BIN
      assets/resources/dark_resources/panelize32.png
  44. BIN
      assets/resources/dark_resources/settings18.png
  45. BIN
      assets/resources/dark_resources/shell20.png
  46. BIN
      assets/resources/edit_file16.png
  47. BIN
      assets/resources/edit_file32.png
  48. BIN
      assets/resources/etch_32.png
  49. BIN
      assets/resources/grid32.png
  50. BIN
      assets/resources/grid_lines32.png
  51. BIN
      assets/resources/hud16.png
  52. BIN
      assets/resources/hud_32.png
  53. BIN
      assets/resources/iso_16.png
  54. BIN
      assets/resources/panelize32.png
  55. BIN
      assets/resources/settings18.png
  56. BIN
      assets/resources/shell20.png
  57. 90 515
      camlib.py
  58. 20 42
      defaults.py
  59. 48 29
      flatcamEditors/FlatCAMExcEditor.py
  60. 71 67
      flatcamEditors/FlatCAMGeoEditor.py
  61. 125 119
      flatcamEditors/FlatCAMGrbEditor.py
  62. 4 4
      flatcamEditors/FlatCAMTextEditor.py
  63. 0 0
      flatcamEditors/__init__.py
  64. 105 2449
      flatcamGUI/FlatCAMGUI.py
  65. 14 391
      flatcamGUI/GUIElements.py
  66. 351 151
      flatcamGUI/ObjectUI.py
  67. 14 152
      flatcamGUI/PlotCanvas.py
  68. 9 233
      flatcamGUI/PlotCanvasLegacy.py
  69. 0 1
      flatcamGUI/VisPyCanvas.py
  70. 0 0
      flatcamGUI/VisPyData/data/fonts/opensans-regular.ttf
  71. 0 0
      flatcamGUI/VisPyData/data/freetype/freetype253.dll
  72. 0 0
      flatcamGUI/VisPyData/data/freetype/freetype253_x64.dll
  73. 0 0
      flatcamGUI/VisPyPatches.py
  74. 0 0
      flatcamGUI/VisPyTesselators.py
  75. 1 1
      flatcamGUI/VisPyVisuals.py
  76. 0 0
      flatcamGUI/__init__.py
  77. 19 0
      flatcamGUI/preferences/OptionsGroupUI.py
  78. 218 184
      flatcamGUI/preferences/PreferencesUIManager.py
  79. 2 2
      flatcamGUI/preferences/__init__.py
  80. 37 8
      flatcamGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py
  81. 142 23
      flatcamGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py
  82. 3 3
      flatcamGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py
  83. 3 3
      flatcamGUI/preferences/cncjob/CNCJobPreferencesUI.py
  84. 0 0
      flatcamGUI/preferences/cncjob/__init__.py
  85. 5 5
      flatcamGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py
  86. 3 3
      flatcamGUI/preferences/excellon/ExcellonEditorPrefGroupUI.py
  87. 3 3
      flatcamGUI/preferences/excellon/ExcellonExpPrefGroupUI.py
  88. 86 137
      flatcamGUI/preferences/excellon/ExcellonGenPrefGroupUI.py
  89. 6 6
      flatcamGUI/preferences/excellon/ExcellonOptPrefGroupUI.py
  90. 7 7
      flatcamGUI/preferences/excellon/ExcellonPreferencesUI.py
  91. 0 0
      flatcamGUI/preferences/excellon/__init__.py
  92. 50 32
      flatcamGUI/preferences/general/GeneralAPPSetGroupUI.py
  93. 6 5
      flatcamGUI/preferences/general/GeneralAppPrefGroupUI.py
  94. 303 73
      flatcamGUI/preferences/general/GeneralGUIPrefGroupUI.py
  95. 4 4
      flatcamGUI/preferences/general/GeneralPreferencesUI.py
  96. 0 0
      flatcamGUI/preferences/general/__init__.py
  97. 12 20
      flatcamGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py
  98. 3 3
      flatcamGUI/preferences/geometry/GeometryEditorPrefGroupUI.py
  99. 32 18
      flatcamGUI/preferences/geometry/GeometryGenPrefGroupUI.py
  100. 5 6
      flatcamGUI/preferences/geometry/GeometryOptPrefGroupUI.py

+ 0 - 181
AppGUI/ColumnarFlowLayout.py

@@ -1,181 +0,0 @@
-# ##########################################################
-# 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)

+ 0 - 327
AppGUI/preferences/OptionUI.py

@@ -1,327 +0,0 @@
-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

+ 0 - 77
AppGUI/preferences/OptionsGroupUI.py

@@ -1,77 +0,0 @@
-# ##########################################################
-# 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

+ 0 - 41
AppGUI/preferences/PreferencesSectionUI.py

@@ -1,41 +0,0 @@
-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

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

@@ -1,302 +0,0 @@
-
-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"]

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

@@ -1,100 +0,0 @@
-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()

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

@@ -1,81 +0,0 @@
-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()

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

@@ -1,320 +0,0 @@
-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()

+ 0 - 394
AppObjects/AppObject.py

@@ -1,394 +0,0 @@
-# ###########################################################
-# 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()

+ 0 - 440
AppTools/ToolCorners.py

@@ -1,440 +0,0 @@
-# ##########################################################
-# FlatCAM: 2D Post-processing for Manufacturing            #
-# File Author: Marius Adrian Stanciu (c)                   #
-# Date: 5/17/2020                                         #
-# MIT Licence                                              #
-# ##########################################################
-
-from PyQt5 import QtWidgets, QtCore
-
-from AppTool import AppTool
-from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, FCButton
-
-from shapely.geometry import MultiPolygon, LineString
-
-from copy import deepcopy
-import logging
-
-import gettext
-import AppTranslation as fcTranslate
-import builtins
-
-fcTranslate.apply_language('strings')
-if '_' not in builtins.__dict__:
-    _ = gettext.gettext
-
-log = logging.getLogger('base')
-
-
-class ToolCorners(AppTool):
-
-    toolName = _("Corner Markers Tool")
-
-    def __init__(self, app):
-        AppTool.__init__(self, app)
-
-        self.app = app
-        self.canvas = self.app.plotcanvas
-
-        self.decimals = self.app.decimals
-        self.units = ''
-
-        # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(title_label)
-        self.layout.addWidget(QtWidgets.QLabel(''))
-
-        # Gerber object #
-        self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("GERBER"))
-        self.object_label.setToolTip(
-            _("The Gerber object to which will be added corner markers.")
-        )
-        self.object_combo = FCComboBox()
-        self.object_combo.setModel(self.app.collection)
-        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.object_combo.is_last = True
-        self.object_combo.obj_type = "Gerber"
-
-        self.layout.addWidget(self.object_label)
-        self.layout.addWidget(self.object_combo)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.layout.addWidget(separator_line)
-
-        self.points_label = QtWidgets.QLabel('<b>%s:</b>' % _('Locations'))
-        self.points_label.setToolTip(
-            _("Locations where to place corner markers.")
-        )
-        self.layout.addWidget(self.points_label)
-
-        # BOTTOM LEFT
-        self.bl_cb = FCCheckBox(_("Bottom Left"))
-        self.layout.addWidget(self.bl_cb)
-
-        # BOTTOM RIGHT
-        self.br_cb = FCCheckBox(_("Bottom Right"))
-        self.layout.addWidget(self.br_cb)
-
-        # TOP LEFT
-        self.tl_cb = FCCheckBox(_("Top Left"))
-        self.layout.addWidget(self.tl_cb)
-
-        # TOP RIGHT
-        self.tr_cb = FCCheckBox(_("Top Right"))
-        self.layout.addWidget(self.tr_cb)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.layout.addWidget(separator_line)
-
-        # Toggle ALL
-        self.toggle_all_cb = FCCheckBox(_("Toggle ALL"))
-        self.layout.addWidget(self.toggle_all_cb)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.layout.addWidget(separator_line)
-
-        # ## Grid Layout
-        grid_lay = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_lay)
-        grid_lay.setColumnStretch(0, 0)
-        grid_lay.setColumnStretch(1, 1)
-
-        self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
-        self.param_label.setToolTip(
-            _("Parameters used for this tool.")
-        )
-        grid_lay.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(callback=self.confirmation_message)
-        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)
-
-        grid_lay.addWidget(self.thick_label, 1, 0)
-        grid_lay.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(callback=self.confirmation_message)
-        self.l_entry.set_range(-9999.9999, 9999.9999)
-        self.l_entry.set_precision(self.decimals)
-        self.l_entry.setSingleStep(10 ** -self.decimals)
-
-        grid_lay.addWidget(self.l_label, 2, 0)
-        grid_lay.addWidget(self.l_entry, 2, 1)
-
-        # Margin #
-        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
-        self.margin_label.setToolTip(
-            _("Bounding box margin.")
-        )
-        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.margin_entry.set_range(-9999.9999, 9999.9999)
-        self.margin_entry.set_precision(self.decimals)
-        self.margin_entry.setSingleStep(0.1)
-
-        grid_lay.addWidget(self.margin_label, 3, 0)
-        grid_lay.addWidget(self.margin_entry, 3, 1)
-
-        separator_line_2 = QtWidgets.QFrame()
-        separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line_2, 4, 0, 1, 2)
-
-        # ## Insert Corner Marker
-        self.add_marker_button = FCButton(_("Add Marker"))
-        self.add_marker_button.setToolTip(
-            _("Will add corner markers to the selected Gerber file.")
-        )
-        self.add_marker_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay.addWidget(self.add_marker_button, 11, 0, 1, 2)
-
-        self.layout.addStretch()
-
-        # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
-        self.reset_button.setToolTip(
-            _("Will reset the tool parameters.")
-        )
-        self.reset_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(self.reset_button)
-
-        # Objects involved in Copper thieving
-        self.grb_object = None
-
-        # store the flattened geometry here:
-        self.flat_geometry = []
-
-        # Tool properties
-        self.fid_dia = None
-
-        self.grb_steps_per_circle = self.app.defaults["gerber_circle_steps"]
-
-        # SIGNALS
-        self.add_marker_button.clicked.connect(self.add_markers)
-        self.toggle_all_cb.toggled.connect(self.on_toggle_all)
-
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolCorners()")
-
-        if toggle:
-            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
-            else:
-                try:
-                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        # if tab is populated with the tool but it does not have the focus, focus on it
-                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
-                            # focus on Tool Tab
-                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
-                        else:
-                            self.app.ui.splitter.setSizes([0, 1])
-                except AttributeError:
-                    pass
-        else:
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
-
-        AppTool.run(self)
-
-        self.set_tool_ui()
-
-        self.app.ui.notebook.setTabText(2, _("Corners Tool"))
-
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Alt+M', **kwargs)
-
-    def set_tool_ui(self):
-        self.units = self.app.defaults['units']
-        self.thick_entry.set_value(self.app.defaults["tools_corners_thickness"])
-        self.l_entry.set_value(float(self.app.defaults["tools_corners_length"]))
-        self.margin_entry.set_value(float(self.app.defaults["tools_corners_margin"]))
-        self.toggle_all_cb.set_value(False)
-
-    def on_toggle_all(self, val):
-        self.bl_cb.set_value(val)
-        self.br_cb.set_value(val)
-        self.tl_cb.set_value(val)
-        self.tr_cb.set_value(val)
-
-    def add_markers(self):
-        self.app.call_source = "corners_tool"
-        tl_state = self.tl_cb.get_value()
-        tr_state = self.tr_cb.get_value()
-        bl_state = self.bl_cb.get_value()
-        br_state = self.br_cb.get_value()
-
-        # get the Gerber object on which the corner marker will be inserted
-        selection_index = self.object_combo.currentIndex()
-        model_index = self.app.collection.index(selection_index, 0, self.object_combo.rootModelIndex())
-
-        try:
-            self.grb_object = model_index.internalPointer().obj
-        except Exception as e:
-            log.debug("ToolCorners.add_markers() --> %s" % str(e))
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
-            return
-
-        xmin, ymin, xmax, ymax = self.grb_object.bounds()
-        points = {}
-        if tl_state:
-            points['tl'] = (xmin, ymax)
-        if tr_state:
-            points['tr'] = (xmax, ymax)
-        if bl_state:
-            points['bl'] = (xmin, ymin)
-        if br_state:
-            points['br'] = (xmax, ymin)
-
-        self.add_corners_geo(points, g_obj=self.grb_object)
-
-        self.grb_object.source_file = self.app.export_gerber(obj_name=self.grb_object.options['name'],
-                                                             filename=None,
-                                                             local_use=self.grb_object, use_thread=False)
-        self.on_exit()
-
-    def add_corners_geo(self, points_storage, g_obj):
-        """
-        Add geometry to the solid_geometry of the copper Gerber object
-
-        :param points_storage:  a dictionary holding the points where to add corners
-        :param g_obj:           the Gerber object where to add the geometry
-        :return:                None
-        """
-
-        line_thickness = self.thick_entry.get_value()
-        line_length = self.l_entry.get_value()
-        margin = self.margin_entry.get_value()
-
-        geo_list = []
-
-        if not points_storage:
-            self.app.inform.emit("[ERROR_NOTCL] %s." % _("Please select at least a location"))
-            return
-
-        for key in points_storage:
-            if key == 'tl':
-                pt = points_storage[key]
-                x = pt[0] - margin - line_thickness / 2.0
-                y = pt[1] + margin + line_thickness / 2.0
-                line_geo_hor = LineString([
-                    (x, y), (x + line_length, y)
-                ])
-                line_geo_vert = LineString([
-                    (x, y), (x, y - line_length)
-                ])
-                geo_list.append(line_geo_hor)
-                geo_list.append(line_geo_vert)
-            if key == 'tr':
-                pt = points_storage[key]
-                x = pt[0] + margin + line_thickness / 2.0
-                y = pt[1] + margin + line_thickness / 2.0
-                line_geo_hor = LineString([
-                    (x, y), (x - line_length, y)
-                ])
-                line_geo_vert = LineString([
-                    (x, y), (x, y - line_length)
-                ])
-                geo_list.append(line_geo_hor)
-                geo_list.append(line_geo_vert)
-            if key == 'bl':
-                pt = points_storage[key]
-                x = pt[0] - margin - line_thickness / 2.0
-                y = pt[1] - margin - line_thickness / 2.0
-                line_geo_hor = LineString([
-                    (x, y), (x + line_length, y)
-                ])
-                line_geo_vert = LineString([
-                    (x, y), (x, y + line_length)
-                ])
-                geo_list.append(line_geo_hor)
-                geo_list.append(line_geo_vert)
-            if key == 'br':
-                pt = points_storage[key]
-                x = pt[0] + margin + line_thickness / 2.0
-                y = pt[1] - margin - line_thickness / 2.0
-                line_geo_hor = LineString([
-                    (x, y), (x - line_length, y)
-                ])
-                line_geo_vert = LineString([
-                    (x, y), (x, y + line_length)
-                ])
-                geo_list.append(line_geo_hor)
-                geo_list.append(line_geo_vert)
-
-        aperture_found = None
-        for ap_id, ap_val in g_obj.apertures.items():
-            if ap_val['type'] == 'C' and ap_val['size'] == line_thickness:
-                aperture_found = ap_id
-                break
-
-        geo_buff_list = []
-        if aperture_found:
-            for geo in geo_list:
-                geo_buff = geo.buffer(line_thickness / 2.0, resolution=self.grb_steps_per_circle, join_style=2)
-                geo_buff_list.append(geo_buff)
-
-                dict_el = {}
-                dict_el['follow'] = geo
-                dict_el['solid'] = geo_buff
-                g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
-        else:
-            ap_keys = list(g_obj.apertures.keys())
-            if ap_keys:
-                new_apid = str(int(max(ap_keys)) + 1)
-            else:
-                new_apid = '10'
-
-            g_obj.apertures[new_apid] = {}
-            g_obj.apertures[new_apid]['type'] = 'C'
-            g_obj.apertures[new_apid]['size'] = line_thickness
-            g_obj.apertures[new_apid]['geometry'] = []
-
-            for geo in geo_list:
-                geo_buff = geo.buffer(line_thickness / 2.0, resolution=self.grb_steps_per_circle, join_style=3)
-                geo_buff_list.append(geo_buff)
-
-                dict_el = {}
-                dict_el['follow'] = geo
-                dict_el['solid'] = geo_buff
-                g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
-
-        s_list = []
-        if g_obj.solid_geometry:
-            try:
-                for poly in g_obj.solid_geometry:
-                    s_list.append(poly)
-            except TypeError:
-                s_list.append(g_obj.solid_geometry)
-
-        geo_buff_list = MultiPolygon(geo_buff_list)
-        geo_buff_list = geo_buff_list.buffer(0)
-        for poly in geo_buff_list:
-            s_list.append(poly)
-        g_obj.solid_geometry = MultiPolygon(s_list)
-
-    def replot(self, obj, run_thread=True):
-        def worker_task():
-            with self.app.proc_container.new('%s...' % _("Plotting")):
-                obj.plot()
-
-        if run_thread:
-            self.app.worker_task.emit({'fcn': worker_task, 'params': []})
-        else:
-            worker_task()
-
-    def on_exit(self):
-        # plot the object
-        try:
-            self.replot(obj=self.grb_object)
-        except (AttributeError, TypeError):
-            return
-
-        # update the bounding box values
-        try:
-            a, b, c, d = self.grb_object.bounds()
-            self.grb_object.options['xmin'] = a
-            self.grb_object.options['ymin'] = b
-            self.grb_object.options['xmax'] = c
-            self.grb_object.options['ymax'] = d
-        except Exception as e:
-            log.debug("ToolCorners.on_exit() copper_obj bounds error --> %s" % str(e))
-
-        # reset the variables
-        self.grb_object = None
-
-        self.app.call_source = "app"
-        self.app.inform.emit('[success] %s' % _("Corners Tool exit."))

+ 0 - 455
AppTools/ToolEtchCompensation.py

@@ -1,455 +0,0 @@
-# ##########################################################
-# FlatCAM: 2D Post-processing for Manufacturing            #
-# File Author: Marius Adrian Stanciu (c)                   #
-# Date: 2/14/2020                                          #
-# MIT Licence                                              #
-# ##########################################################
-
-from PyQt5 import QtWidgets, QtCore
-
-from AppTool import AppTool
-from AppGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox, NumericalEvalEntry, FCEntry
-
-from shapely.ops import unary_union
-
-from copy import deepcopy
-import math
-
-import logging
-import gettext
-import AppTranslation as fcTranslate
-import builtins
-
-fcTranslate.apply_language('strings')
-if '_' not in builtins.__dict__:
-    _ = gettext.gettext
-
-log = logging.getLogger('base')
-
-
-class ToolEtchCompensation(AppTool):
-
-    toolName = _("Etch Compensation Tool")
-
-    def __init__(self, app):
-        self.app = app
-        self.decimals = self.app.decimals
-
-        AppTool.__init__(self, app)
-
-        self.tools_frame = QtWidgets.QFrame()
-        self.tools_frame.setContentsMargins(0, 0, 0, 0)
-        self.layout.addWidget(self.tools_frame)
-        self.tools_box = QtWidgets.QVBoxLayout()
-        self.tools_box.setContentsMargins(0, 0, 0, 0)
-        self.tools_frame.setLayout(self.tools_box)
-
-        # Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.tools_box.addWidget(title_label)
-
-        # Grid Layout
-        grid0 = QtWidgets.QGridLayout()
-        grid0.setColumnStretch(0, 0)
-        grid0.setColumnStretch(1, 1)
-        self.tools_box.addLayout(grid0)
-
-        grid0.addWidget(QtWidgets.QLabel(''), 0, 0, 1, 2)
-
-        # Target Gerber Object
-        self.gerber_combo = FCComboBox()
-        self.gerber_combo.setModel(self.app.collection)
-        self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.gerber_combo.is_last = True
-        self.gerber_combo.obj_type = "Gerber"
-
-        self.gerber_label = QtWidgets.QLabel('<b>%s:</b>' % _("GERBER"))
-        self.gerber_label.setToolTip(
-            _("Gerber object that will be inverted.")
-        )
-
-        grid0.addWidget(self.gerber_label, 1, 0, 1, 2)
-        grid0.addWidget(self.gerber_combo, 2, 0, 1, 2)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 3, 0, 1, 2)
-
-        self.util_label = QtWidgets.QLabel("<b>%s:</b>" % _("Utilities"))
-        self.util_label.setToolTip('%s.' % _("Conversion utilities"))
-
-        grid0.addWidget(self.util_label, 4, 0, 1, 2)
-
-        # Oz to um conversion
-        self.oz_um_label = QtWidgets.QLabel('%s:' % _('Oz to Microns'))
-        self.oz_um_label.setToolTip(
-            _("Will convert from oz thickness to microns [um].\n"
-              "Can use formulas with operators: /, *, +, -, %, .\n"
-              "The real numbers use the dot decimals separator.")
-        )
-        grid0.addWidget(self.oz_um_label, 5, 0, 1, 2)
-
-        hlay_1 = QtWidgets.QHBoxLayout()
-
-        self.oz_entry = NumericalEvalEntry(border_color='#0069A9')
-        self.oz_entry.setPlaceholderText(_("Oz value"))
-        self.oz_to_um_entry = FCEntry()
-        self.oz_to_um_entry.setPlaceholderText(_("Microns value"))
-        self.oz_to_um_entry.setReadOnly(True)
-
-        hlay_1.addWidget(self.oz_entry)
-        hlay_1.addWidget(self.oz_to_um_entry)
-        grid0.addLayout(hlay_1, 6, 0, 1, 2)
-
-        # Mils to um conversion
-        self.mils_um_label = QtWidgets.QLabel('%s:' % _('Mils to Microns'))
-        self.mils_um_label.setToolTip(
-            _("Will convert from mils to microns [um].\n"
-              "Can use formulas with operators: /, *, +, -, %, .\n"
-              "The real numbers use the dot decimals separator.")
-        )
-        grid0.addWidget(self.mils_um_label, 7, 0, 1, 2)
-
-        hlay_2 = QtWidgets.QHBoxLayout()
-
-        self.mils_entry = NumericalEvalEntry(border_color='#0069A9')
-        self.mils_entry.setPlaceholderText(_("Mils value"))
-        self.mils_to_um_entry = FCEntry()
-        self.mils_to_um_entry.setPlaceholderText(_("Microns value"))
-        self.mils_to_um_entry.setReadOnly(True)
-
-        hlay_2.addWidget(self.mils_entry)
-        hlay_2.addWidget(self.mils_to_um_entry)
-        grid0.addLayout(hlay_2, 8, 0, 1, 2)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 9, 0, 1, 2)
-
-        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.param_label.setToolTip('%s.' % _("Parameters for this tool"))
-
-        grid0.addWidget(self.param_label, 10, 0, 1, 2)
-
-        # Thickness
-        self.thick_label = QtWidgets.QLabel('%s:' % _('Copper Thickness'))
-        self.thick_label.setToolTip(
-            _("The thickness of the copper foil.\n"
-              "In microns [um].")
-        )
-        self.thick_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.thick_entry.set_precision(self.decimals)
-        self.thick_entry.set_range(0.0000, 9999.9999)
-        self.thick_entry.setObjectName(_("Thickness"))
-
-        grid0.addWidget(self.thick_label, 12, 0)
-        grid0.addWidget(self.thick_entry, 12, 1)
-
-        self.ratio_label = QtWidgets.QLabel('%s:' % _("Ratio"))
-        self.ratio_label.setToolTip(
-            _("The ratio of lateral etch versus depth etch.\n"
-              "Can be:\n"
-              "- custom -> the user will enter a custom value\n"
-              "- preselection -> value which depends on a selection of etchants")
-        )
-        self.ratio_radio = RadioSet([
-            {'label': _('Etch Factor'), 'value': 'factor'},
-            {'label': _('Etchants list'), 'value': 'etch_list'},
-            {'label': _('Manual offset'), 'value': 'manual'}
-        ], orientation='vertical', stretch=False)
-
-        grid0.addWidget(self.ratio_label, 14, 0, 1, 2)
-        grid0.addWidget(self.ratio_radio, 16, 0, 1, 2)
-
-        # Etchants
-        self.etchants_label = QtWidgets.QLabel('%s:' % _('Etchants'))
-        self.etchants_label.setToolTip(
-            _("A list of etchants.")
-        )
-        self.etchants_combo = FCComboBox(callback=self.confirmation_message)
-        self.etchants_combo.setObjectName(_("Etchants"))
-        self.etchants_combo.addItems(["CuCl2", "Fe3Cl", _("Alkaline baths")])
-
-        grid0.addWidget(self.etchants_label, 18, 0)
-        grid0.addWidget(self.etchants_combo, 18, 1)
-
-        # Etch Factor
-        self.factor_label = QtWidgets.QLabel('%s:' % _('Etch factor'))
-        self.factor_label.setToolTip(
-            _("The ratio between depth etch and lateral etch .\n"
-              "Accepts real numbers and formulas using the operators: /,*,+,-,%")
-        )
-        self.factor_entry = NumericalEvalEntry(border_color='#0069A9')
-        self.factor_entry.setPlaceholderText(_("Real number or formula"))
-        self.factor_entry.setObjectName(_("Etch_factor"))
-
-        grid0.addWidget(self.factor_label, 19, 0)
-        grid0.addWidget(self.factor_entry, 19, 1)
-
-        # Manual Offset
-        self.offset_label = QtWidgets.QLabel('%s:' % _('Offset'))
-        self.offset_label.setToolTip(
-            _("Value with which to increase or decrease (buffer)\n"
-              "the copper features. In microns [um].")
-        )
-        self.offset_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.offset_entry.set_precision(self.decimals)
-        self.offset_entry.set_range(-9999.9999, 9999.9999)
-        self.offset_entry.setObjectName(_("Offset"))
-
-        grid0.addWidget(self.offset_label, 20, 0)
-        grid0.addWidget(self.offset_entry, 20, 1)
-
-        # Hide the Etchants and Etch factor
-        self.etchants_label.hide()
-        self.etchants_combo.hide()
-        self.factor_label.hide()
-        self.factor_entry.hide()
-        self.offset_label.hide()
-        self.offset_entry.hide()
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 22, 0, 1, 2)
-
-        self.compensate_btn = FCButton(_('Compensate'))
-        self.compensate_btn.setToolTip(
-            _("Will increase the copper features thickness to compensate the lateral etch.")
-        )
-        self.compensate_btn.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid0.addWidget(self.compensate_btn, 24, 0, 1, 2)
-
-        self.tools_box.addStretch()
-
-        # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
-        self.reset_button.setToolTip(
-            _("Will reset the tool parameters.")
-        )
-        self.reset_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.tools_box.addWidget(self.reset_button)
-
-        self.compensate_btn.clicked.connect(self.on_compensate)
-        self.reset_button.clicked.connect(self.set_tool_ui)
-        self.ratio_radio.activated_custom.connect(self.on_ratio_change)
-
-        self.oz_entry.textChanged.connect(self.on_oz_conversion)
-        self.mils_entry.textChanged.connect(self.on_mils_conversion)
-
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='', **kwargs)
-
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolEtchCompensation()")
-        log.debug("ToolEtchCompensation() is running ...")
-
-        if toggle:
-            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
-            else:
-                try:
-                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        # if tab is populated with the tool but it does not have the focus, focus on it
-                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
-                            # focus on Tool Tab
-                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
-                        else:
-                            self.app.ui.splitter.setSizes([0, 1])
-                except AttributeError:
-                    pass
-        else:
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
-
-        AppTool.run(self)
-        self.set_tool_ui()
-
-        self.app.ui.notebook.setTabText(2, _("Etch Compensation Tool"))
-
-    def set_tool_ui(self):
-        self.thick_entry.set_value(18.0)
-        self.ratio_radio.set_value('factor')
-
-    def on_ratio_change(self, val):
-        """
-        Called on activated_custom signal of the RadioSet GUI element self.radio_ratio
-
-        :param val:     'c' or 'p': 'c' means custom factor and 'p' means preselected etchants
-        :type val:      str
-        :return:        None
-        :rtype:
-        """
-        if val == 'factor':
-            self.etchants_label.hide()
-            self.etchants_combo.hide()
-            self.factor_label.show()
-            self.factor_entry.show()
-            self.offset_label.hide()
-            self.offset_entry.hide()
-        elif val == 'etch_list':
-            self.etchants_label.show()
-            self.etchants_combo.show()
-            self.factor_label.hide()
-            self.factor_entry.hide()
-            self.offset_label.hide()
-            self.offset_entry.hide()
-        else:
-            self.etchants_label.hide()
-            self.etchants_combo.hide()
-            self.factor_label.hide()
-            self.factor_entry.hide()
-            self.offset_label.show()
-            self.offset_entry.show()
-
-    def on_oz_conversion(self, txt):
-        try:
-            val = eval(txt)
-            # oz thickness to mils by multiplying with 1.37
-            # mils to microns by multiplying with 25.4
-            val *= 34.798
-        except Exception:
-            self.oz_to_um_entry.set_value('')
-            return
-        self.oz_to_um_entry.set_value(val, self.decimals)
-
-    def on_mils_conversion(self, txt):
-        try:
-            val = eval(txt)
-            val *= 25.4
-        except Exception:
-            self.mils_to_um_entry.set_value('')
-            return
-        self.mils_to_um_entry.set_value(val, self.decimals)
-
-    def on_compensate(self):
-        log.debug("ToolEtchCompensation.on_compensate()")
-
-        ratio_type = self.ratio_radio.get_value()
-        thickness = self.thick_entry.get_value() / 1000     # in microns
-
-        grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
-        obj_name = self.gerber_combo.currentText()
-
-        outname = obj_name + "_comp"
-
-        # Get source object.
-        try:
-            grb_obj = self.app.collection.get_by_name(obj_name)
-        except Exception as e:
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
-            return "Could not retrieve object: %s with error: %s" % (obj_name, str(e))
-
-        if grb_obj is None:
-            if obj_name == '':
-                obj_name = 'None'
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
-            return
-
-        if ratio_type == 'factor':
-            etch_factor = 1 / self.factor_entry.get_value()
-            offset = thickness / etch_factor
-        elif ratio_type == 'etch_list':
-            etchant = self.etchants_combo.get_value()
-            if etchant == "CuCl2":
-                etch_factor = 0.33
-            else:
-                etch_factor = 0.25
-            offset = thickness / etch_factor
-        else:
-            offset = self.offset_entry.get_value() / 1000   # in microns
-
-        try:
-            __ = iter(grb_obj.solid_geometry)
-        except TypeError:
-            grb_obj.solid_geometry = list(grb_obj.solid_geometry)
-
-        new_solid_geometry = []
-
-        for poly in grb_obj.solid_geometry:
-            new_solid_geometry.append(poly.buffer(offset, int(grb_circle_steps)))
-        new_solid_geometry = unary_union(new_solid_geometry)
-
-        new_options = {}
-        for opt in grb_obj.options:
-            new_options[opt] = deepcopy(grb_obj.options[opt])
-
-        new_apertures = deepcopy(grb_obj.apertures)
-
-        # update the apertures attributes (keys in the apertures dict)
-        for ap in new_apertures:
-            type = new_apertures[ap]['type']
-            for k in new_apertures[ap]:
-                if type == 'R' or type == 'O':
-                    if k == 'width' or k == 'height':
-                        new_apertures[ap][k] += offset
-                else:
-                    if k == 'size' or k == 'width' or k == 'height':
-                        new_apertures[ap][k] += offset
-
-                if k == 'geometry':
-                    for geo_el in new_apertures[ap][k]:
-                        if 'solid' in geo_el:
-                            geo_el['solid'] = geo_el['solid'].buffer(offset, int(grb_circle_steps))
-
-        # in case of 'R' or 'O' aperture type we need to update the aperture 'size' after
-        # the 'width' and 'height' keys were updated
-        for ap in new_apertures:
-            type = new_apertures[ap]['type']
-            for k in new_apertures[ap]:
-                if type == 'R' or type == 'O':
-                    if k == 'size':
-                        new_apertures[ap][k] = math.sqrt(
-                            new_apertures[ap]['width'] ** 2 + new_apertures[ap]['height'] ** 2)
-
-        def init_func(new_obj, app_obj):
-            """
-            Init a new object in FlatCAM Object collection
-
-            :param new_obj:     New object
-            :type new_obj:      ObjectCollection
-            :param app_obj:     App
-            :type app_obj:      App_Main.App
-            :return:            None
-            :rtype:
-            """
-            new_obj.options.update(new_options)
-            new_obj.options['name'] = outname
-            new_obj.fill_color = deepcopy(grb_obj.fill_color)
-            new_obj.outline_color = deepcopy(grb_obj.outline_color)
-
-            new_obj.apertures = deepcopy(new_apertures)
-
-            new_obj.solid_geometry = deepcopy(new_solid_geometry)
-            new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
-                                                         local_use=new_obj, use_thread=False)
-
-        self.app.app_obj.new_object('gerber', outname, init_func)
-
-    def reset_fields(self):
-        self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-
-    @staticmethod
-    def poly2rings(poly):
-        return [poly.exterior] + [interior for interior in poly.interiors]
-# end of file

+ 0 - 3018
AppTools/ToolIsolation.py

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

+ 0 - 361
AppTools/ToolPDF.py

@@ -1,361 +0,0 @@
-# ##########################################################
-# FlatCAM: 2D Post-processing for Manufacturing            #
-# File Author: Marius Adrian Stanciu (c)                   #
-# Date: 4/23/2019                                          #
-# MIT Licence                                              #
-# ##########################################################
-
-from PyQt5 import QtWidgets, QtCore
-
-from AppTool import AppTool
-
-from AppParsers.ParsePDF import PdfParser, grace
-from shapely.geometry import Point, MultiPolygon
-from shapely.ops import unary_union
-
-from copy import deepcopy
-
-import zlib
-import re
-import time
-import logging
-import traceback
-
-import gettext
-import AppTranslation as fcTranslate
-import builtins
-
-fcTranslate.apply_language('strings')
-if '_' not in builtins.__dict__:
-    _ = gettext.gettext
-
-log = logging.getLogger('base')
-
-
-class ToolPDF(AppTool):
-    """
-    Parse a PDF file.
-    Reference here: https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf
-    Return a list of geometries
-    """
-    toolName = _("PDF Import Tool")
-
-    def __init__(self, app):
-        AppTool.__init__(self, app)
-        self.app = app
-        self.decimals = self.app.decimals
-
-        self.stream_re = re.compile(b'.*?FlateDecode.*?stream(.*?)endstream', re.S)
-
-        self.pdf_decompressed = {}
-
-        # key = file name and extension
-        # value is a dict to store the parsed content of the PDF
-        self.pdf_parsed = {}
-
-        # QTimer for periodic check
-        self.check_thread = QtCore.QTimer()
-
-        # Every time a parser is started we add a promise; every time a parser finished we remove a promise
-        # when empty we start the layer rendering
-        self.parsing_promises = []
-
-        self.parser = PdfParser(app=self.app)
-
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolPDF()")
-
-        self.set_tool_ui()
-        self.on_open_pdf_click()
-
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Ctrl+Q', **kwargs)
-
-    def set_tool_ui(self):
-        pass
-
-    def on_open_pdf_click(self):
-        """
-        File menu callback for opening an PDF file.
-
-        :return: None
-        """
-
-        self.app.defaults.report_usage("ToolPDF.on_open_pdf_click()")
-        self.app.log.debug("ToolPDF.on_open_pdf_click()")
-
-        _filter_ = "Adobe PDF Files (*.pdf);;" \
-                   "All Files (*.*)"
-
-        try:
-            filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open PDF"),
-                                                                   directory=self.app.get_last_folder(),
-                                                                   filter=_filter_)
-        except TypeError:
-            filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open PDF"), filter=_filter_)
-
-        if len(filenames) == 0:
-            self.app.inform.emit('[WARNING_NOTCL] %s.' % _("Open PDF cancelled"))
-        else:
-            # start the parsing timer with a period of 1 second
-            self.periodic_check(1000)
-
-            for filename in filenames:
-                if filename != '':
-                    self.app.worker_task.emit({'fcn': self.open_pdf,
-                                               'params': [filename]})
-
-    def open_pdf(self, filename):
-        short_name = filename.split('/')[-1].split('\\')[-1]
-        self.parsing_promises.append(short_name)
-
-        self.pdf_parsed[short_name] = {}
-        self.pdf_parsed[short_name]['pdf'] = {}
-        self.pdf_parsed[short_name]['filename'] = filename
-
-        self.pdf_decompressed[short_name] = ''
-
-        if self.app.abort_flag:
-            # graceful abort requested by the user
-            raise grace
-
-        with self.app.proc_container.new(_("Parsing PDF file ...")):
-            with open(filename, "rb") as f:
-                pdf = f.read()
-
-            stream_nr = 0
-            for s in re.findall(self.stream_re, pdf):
-                if self.app.abort_flag:
-                    # graceful abort requested by the user
-                    raise grace
-
-                stream_nr += 1
-                log.debug("PDF STREAM: %d\n" % stream_nr)
-                s = s.strip(b'\r\n')
-                try:
-                    self.pdf_decompressed[short_name] += (zlib.decompress(s).decode('UTF-8') + '\r\n')
-                except Exception as e:
-                    self.app.inform.emit('[ERROR_NOTCL] %s: %s\n%s' % (_("Failed to open"), str(filename), str(e)))
-                    log.debug("ToolPDF.open_pdf().obj_init() --> %s" % str(e))
-                    return
-
-            self.pdf_parsed[short_name]['pdf'] = self.parser.parse_pdf(pdf_content=self.pdf_decompressed[short_name])
-            # we used it, now we delete it
-            self.pdf_decompressed[short_name] = ''
-
-        # removal from list is done in a multithreaded way therefore not always the removal can be done
-        # try to remove until it's done
-        try:
-            while True:
-                self.parsing_promises.remove(short_name)
-                time.sleep(0.1)
-        except Exception as e:
-            log.debug("ToolPDF.open_pdf() --> %s" % str(e))
-        self.app.inform.emit('[success] %s: %s' % (_("Opened"),  str(filename)))
-
-    def layer_rendering_as_excellon(self, filename, ap_dict, layer_nr):
-        outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
-
-        # store the points here until reconstitution:
-        # keys are diameters and values are list of (x,y) coords
-        points = {}
-
-        def obj_init(exc_obj, app_obj):
-            clear_geo = [geo_el['clear'] for geo_el in ap_dict['0']['geometry']]
-
-            for geo in clear_geo:
-                xmin, ymin, xmax, ymax = geo.bounds
-                center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
-
-                # for drill bits, even in INCH, it's enough 3 decimals
-                correction_factor = 0.974
-                dia = (xmax - xmin) * correction_factor
-                dia = round(dia, 3)
-                if dia in points:
-                    points[dia].append(center)
-                else:
-                    points[dia] = [center]
-
-            sorted_dia = sorted(points.keys())
-
-            name_tool = 0
-            for dia in sorted_dia:
-                name_tool += 1
-
-                # create tools dictionary
-                spec = {"C": dia, 'solid_geometry': []}
-                exc_obj.tools[str(name_tool)] = spec
-
-                # create drill list of dictionaries
-                for dia_points in points:
-                    if dia == dia_points:
-                        for pt in points[dia_points]:
-                            exc_obj.drills.append({'point': Point(pt), 'tool': str(name_tool)})
-                        break
-
-            ret = exc_obj.create_geometry()
-            if ret == 'fail':
-                log.debug("Could not create geometry for Excellon object.")
-                return "fail"
-            for tool in exc_obj.tools:
-                if exc_obj.tools[tool]['solid_geometry']:
-                    return
-            app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("No geometry found in file"), outname))
-            return "fail"
-
-        with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
-
-            ret_val = self.app.app_obj.new_object("excellon", outname, obj_init, autoselected=False)
-            if ret_val == 'fail':
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _('Open PDF file failed.'))
-                return
-            # Register recent file
-            self.app.file_opened.emit("excellon", filename)
-            # GUI feedback
-            self.app.inform.emit('[success] %s: %s' % (_("Rendered"),  outname))
-
-    def layer_rendering_as_gerber(self, filename, ap_dict, layer_nr):
-        outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
-
-        def obj_init(grb_obj, app_obj):
-
-            grb_obj.apertures = ap_dict
-
-            poly_buff = []
-            follow_buf = []
-            for ap in grb_obj.apertures:
-                for k in grb_obj.apertures[ap]:
-                    if k == 'geometry':
-                        for geo_el in ap_dict[ap][k]:
-                            if 'solid' in geo_el:
-                                poly_buff.append(geo_el['solid'])
-                            if 'follow' in geo_el:
-                                follow_buf.append(geo_el['follow'])
-            poly_buff = unary_union(poly_buff)
-
-            if '0' in grb_obj.apertures:
-                global_clear_geo = []
-                if 'geometry' in grb_obj.apertures['0']:
-                    for geo_el in ap_dict['0']['geometry']:
-                        if 'clear' in geo_el:
-                            global_clear_geo.append(geo_el['clear'])
-
-                if global_clear_geo:
-                    solid = []
-                    for apid in grb_obj.apertures:
-                        if 'geometry' in grb_obj.apertures[apid]:
-                            for elem in grb_obj.apertures[apid]['geometry']:
-                                if 'solid' in elem:
-                                    solid_geo = deepcopy(elem['solid'])
-                                    for clear_geo in global_clear_geo:
-                                        # Make sure that the clear_geo is within the solid_geo otherwise we loose
-                                        # the solid_geometry. We want for clear_geometry just to cut into solid_geometry
-                                        # not to delete it
-                                        if clear_geo.within(solid_geo):
-                                            solid_geo = solid_geo.difference(clear_geo)
-                                        if solid_geo.is_empty:
-                                            solid_geo = elem['solid']
-                                    try:
-                                        for poly in solid_geo:
-                                            solid.append(poly)
-                                    except TypeError:
-                                        solid.append(solid_geo)
-                    poly_buff = deepcopy(MultiPolygon(solid))
-
-            follow_buf = unary_union(follow_buf)
-
-            try:
-                poly_buff = poly_buff.buffer(0.0000001)
-            except ValueError:
-                pass
-            try:
-                poly_buff = poly_buff.buffer(-0.0000001)
-            except ValueError:
-                pass
-
-            grb_obj.solid_geometry = deepcopy(poly_buff)
-            grb_obj.follow_geometry = deepcopy(follow_buf)
-
-        with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
-
-            ret = self.app.app_obj.new_object('gerber', outname, obj_init, autoselected=False)
-            if ret == 'fail':
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _('Open PDF file failed.'))
-                return
-            # Register recent file
-            self.app.file_opened.emit('gerber', filename)
-            # GUI feedback
-            self.app.inform.emit('[success] %s: %s' % (_("Rendered"), outname))
-
-    def periodic_check(self, check_period):
-        """
-        This function starts an QTimer and it will periodically check if parsing was done
-
-        :param check_period: time at which to check periodically if all plots finished to be plotted
-        :return:
-        """
-
-        # self.plot_thread = threading.Thread(target=lambda: self.check_plot_finished(check_period))
-        # self.plot_thread.start()
-        log.debug("ToolPDF --> Periodic Check started.")
-
-        try:
-            self.check_thread.stop()
-        except TypeError:
-            pass
-
-        self.check_thread.setInterval(check_period)
-        try:
-            self.check_thread.timeout.disconnect(self.periodic_check_handler)
-        except (TypeError, AttributeError):
-            pass
-
-        self.check_thread.timeout.connect(self.periodic_check_handler)
-        self.check_thread.start(QtCore.QThread.HighPriority)
-
-    def periodic_check_handler(self):
-        """
-        If the parsing worker finished then start multithreaded rendering
-        :return:
-        """
-        # log.debug("checking parsing --> %s" % str(self.parsing_promises))
-
-        try:
-            if not self.parsing_promises:
-                self.check_thread.stop()
-                log.debug("PDF --> start rendering")
-                # parsing finished start the layer rendering
-                if self.pdf_parsed:
-                    obj_to_delete = []
-                    for object_name in self.pdf_parsed:
-                        if self.app.abort_flag:
-                            # graceful abort requested by the user
-                            raise grace
-
-                        filename = deepcopy(self.pdf_parsed[object_name]['filename'])
-                        pdf_content = deepcopy(self.pdf_parsed[object_name]['pdf'])
-                        obj_to_delete.append(object_name)
-                        for k in pdf_content:
-                            if self.app.abort_flag:
-                                # graceful abort requested by the user
-                                raise grace
-
-                            ap_dict = pdf_content[k]
-                            print(k, ap_dict)
-                            if ap_dict:
-                                layer_nr = k
-                                if k == 0:
-                                    self.app.worker_task.emit({'fcn': self.layer_rendering_as_excellon,
-                                                               'params': [filename, ap_dict, layer_nr]})
-                                else:
-                                    self.app.worker_task.emit({'fcn': self.layer_rendering_as_gerber,
-                                                               'params': [filename, ap_dict, layer_nr]})
-                    # delete the object already processed so it will not be processed again for other objects
-                    # that were opened at the same time; like in drag & drop on AppGUI
-                    for obj_name in obj_to_delete:
-                        if obj_name in self.pdf_parsed:
-                            self.pdf_parsed.pop(obj_name)
-
-                log.debug("ToolPDF --> Periodic check finished.")
-        except Exception:
-            traceback.print_exc()

+ 0 - 45
AppTools/__init__.py

@@ -1,45 +0,0 @@
-
-from AppTools.ToolCalculators import ToolCalculator
-from AppTools.ToolCalibration import ToolCalibration
-
-from AppTools.ToolDblSided import DblSidedTool
-from AppTools.ToolExtractDrills import ToolExtractDrills
-from AppTools.ToolAlignObjects import AlignObjects
-
-from AppTools.ToolFilm import Film
-
-from AppTools.ToolImage import ToolImage
-
-from AppTools.ToolDistance import Distance
-from AppTools.ToolDistanceMin import DistanceMin
-
-from AppTools.ToolMove import ToolMove
-
-from AppTools.ToolCutOut import CutOut
-from AppTools.ToolNCC import NonCopperClear
-from AppTools.ToolPaint import ToolPaint
-from AppTools.ToolIsolation import ToolIsolation
-
-from AppTools.ToolOptimal import ToolOptimal
-
-from AppTools.ToolPanelize import Panelize
-from AppTools.ToolPcbWizard import PcbWizard
-from AppTools.ToolPDF import ToolPDF
-from AppTools.ToolProperties import Properties
-
-from AppTools.ToolQRCode import QRCode
-from AppTools.ToolRulesCheck import RulesCheck
-
-from AppTools.ToolCopperThieving import ToolCopperThieving
-from AppTools.ToolFiducials import ToolFiducials
-
-from AppTools.ToolShell import FCShell
-from AppTools.ToolSolderPaste import SolderPaste
-from AppTools.ToolSub import ToolSub
-
-from AppTools.ToolTransform import ToolTransform
-from AppTools.ToolPunchGerber import ToolPunchGerber
-
-from AppTools.ToolInvertGerber import ToolInvertGerber
-from AppTools.ToolCorners import ToolCorners
-from AppTools.ToolEtchCompensation import ToolEtchCompensation

+ 6 - 229
CHANGELOG.md

@@ -7,229 +7,6 @@ CHANGELOG for FlatCAM beta
 
 =================================================
 
-2.06.2020
-
-- Tcl Shell - added a button to delete the content of the active line
-- Tcl Command Isolate - fixed to work in the new configuration
-- Tcl Command Follow - fixed to work in the new configuration
-- Etch Compensation Tool - added a new etchant: alkaline baths
-- fixed spacing in the status toolbar icons
-- updated the translation files to the latest changes
-- modified behavior of object comboboxes in Paint, NCC and CutOut Tools: now if an object is selected in Project Tab and is of the supported kind in the Tool, it will be auto-selected
-- fixed some more strings
-- updated the Google-translations for the German, Spanish, French
-- updated the Romanian translation
-- replaced the icon for the Editor in Toolbar (both for the normal icons and for icons in dark theme)
-
-1.06.2020
-
-- made the Distance Tool display the angle in values between 0 and 359.9999 degrees
-- changed some strings
-- fixed the warning that old preferences found even for new installation
-- in Paint Tool fixed the message to select a polygon when using the Selection: Single Polygon being overwritten by the "Grid disabled" message
-- more changes in strings throughout the app
-- made some minor changes in the GUI of the FlatCAM Tools
-- in Tools Database made sure that each new tool added has a unique name
-- in AppTool made some methods to be class methods
-- reverted the class methods in AppTool
-- added a button for Transformations Tool in the lower side (common) of the Object UI
-- some other UI changes
-- after using Isolation Tool it will switch automatically to the Geometry UI
-- in Preferences replaced some widgets with a new one that combine a Slider with a Spinner (from David Robertson)
-- in Preferences replaced the widgets that sets colors with a compound one (from David Robertson)
-- made Progressive plotting work in Isolation Tool
-- fix an issue with progressive plotted shapes not being deleted on the end of the job
-- some fixed due of recent changes and some strings changed
-- added a validator for the FCColorEntry GUI element such that only the valid chars are accepted
-- changed the status bar label to have an icon instead of text
-- added a label in status bar that will toggle the Preferences tab
-- made some changes such that that the label in status bar for toggling the Preferences Tab will be updated in various cases of closing the tab
-- changed colors for the status bar labels and added some of the new icons in the gray version
-- remade visibility as threaded - it seems that I can't really squeeze more performance from this
-
-31.05.2020
-
-- structural changes in Preferences from David Robertson
-- made last filter selected for open file to be used next time when opening files (for Excellon, GCode and Gerber files, for now)
-
-30.05.2020
-
-- made confirmation messages for the values that are modified not to be printed in the Shell
-- Isolation Tool: working on the Rest machining: almost there, perhaps I will use multiprocessing
-- Isolation Tool: removed the tools that have empty geometry in case of rest machining
-- Isolation Tool: solved some naming issues
-- Isolation Tool: updated the tools dict with the common parameters value on isolating
-- Fixed a recent change that made the edited Geometry objects in the Geometry Editor not to be plotted after saving changes
-- modified the Tool Database such that when a tool shape is selected as 'V' any change in the Vdia or Vangle or CutZ parameters will update the tool diameter value
-- In Tool Isolation made sure that the use of ESC key while some processes are active will disconnect the mouse events that may be connected, correctly
-- optimized the Gerber UI
-- added a Multi-color checkbox for the Geometry UI (will color differently tool geometry when the geometry is multitool)
-- added a Multi-color checkbox for the Excellon UI (this way colors for each tool are easier to differentiate especially when the diameter is close)
-- made the Shell Dock always show docked
-- fixed NCC Tool behavior when selecting tools for Isolation operation
-
-29.05.2020
-
-- fixed the Tool Isolation when using the 'follow' parameter
-- in Isolation Tool when the Rest machining is checked the combine parameter is set True automatically because the rest machining concept make sense only when all tools are used together
-- some changes in the UI; added in the status bar an icon to control the Shell Dock
-- clicking on the activity icon will replot all objects
-- optimized UI in Tool Isolation
-- overloaded the App inform signal to allow not printing to shell if a second bool parameter is given; modified some GUI messages to use this feature
-- fixed the shell status label status on shell dock close from close button
-- refactored some methods from App class and moved them to plotcanvas (plotcanvaslegacy) class
-- added an label with icon in the status bar, clicking it will toggle (show status) of the X-Y axis on cavnas
-- optimized the UI, added to status bar an icon to toggle the axis 
-- updated the Etch Compensation Tool by adding a new possibility to compensate the lateral etch (manual value)
-- updated the Etch Compensation Tool such that the resulting Gerber object will have the apertures attributes ('size', 'width', 'height') updated to the changes
-
-28.05.2020
-
-- made the visibility change (when using the Spacebar key in Project Tab) to be not threaded and to use the enabled property of the ShapesCollection which should be faster
-- updated the Tool Database class to have the Isolation Tool data
-- Isolation Tool - made to work the adding of tools from database
-- fixed some issues related to using the new Numerical... GUI elements
-- fixed issues in the Tool Subtract
-- remade Tool Subtract to use multiprocessing when processing geometry
-- the resulting Gerber file from Tool Subtract has now the attribute source_file populated
-
-27.05.2020
-
-- working on Isolation Tool: made to work the Isolation with multiple tools without rest machining
-
-26.05.2020
-
-- working on Isolation Tool: made to work the tool parameters data to GUI and GUI to data
-- Isolation Tool: reworked the GUI
-- if there is a Gerber object selected then in Isolation Tool the Gerber object combobox will show that object name as current
-- made the Project Tree items not editable by clicking on selected Tree items (the object rename can still be done in the Selected tab)
-- working on Isolation Tool: added a Preferences section in Edit -> Preferences and updated their usage within the Isolation tool
-- fixed milling drills not plotting the resulting Geometry object
-- all tuple entries in the Preferences UI are now protected against letter entry
-- all entries in the Preferences UI that have numerical entry are protected now against letters
-- cleaned the Preferences UI in the Gerber area
-- minor UI changes
-
-25.05.2020
-
-- updated the GUI fields for the Scale and Offset in the Object UI to allow only numeric values and operators in the list [/,*,+,-], spaces, dots and comma
-- modified the Etch Compensation Tool and added conversion utilities from Oz thickenss and mils to microns
-- added a Toggle All checkbox to Corner Markers Tool
-- added an Icon to the MessageBox that asks for saving if the user try to close the app and there is some unsaved work 
-- changed and added some icons
-- fixed the Shortcuts Tab to reflect the actual current shortcut keys
-- started to work on moving the Isolation Routing from the Gerber Object UI to it's own tool
-- created a new tool: Isolation Routing Tool: work in progress
-- some fixes in NCC Tool
-- added a dialog in Menu -> Help -> ReadMe?
-
-24.05.2020
-
-- changes some icons
-- added a new GUI element which is a evaluated LineEdit that accepts only float numbers and /,*,+,-,% chars
-- finished the Etch Compensation Tool
-- fixed unreliable work of Gerber Editor and optimized the App.editor2object() method
-- updated the Gerber parser such that it will parse correctly Gerber files that have only one solid polygon inside with multiple clear polygons (like those generated by the Invert Tool)
-- fixed a small bug in the Geometry UI that made updating the storage from GUI not to work
-- some small changes in Gerber Editor
-
-23.05.2020
-
-- fixed a issue when testing for Exclusion areas overlap over the Geometry object solid_geometry
-
-22.05.2020
-
-- fixed the algorithm for calculating closest points in the Exclusion areas
-- added the Exclusion zones processing to Geometry GCode generation
-
-21.05.2020
-
-- added the Exclusion zones processing to Excellon GCode generation
-- fixed a non frequent plotting problem for CNCJob objects made out of Excellon objects
-
-19.05.2020
-
-- updated the Italian language (translation incomplete)
-- updated all the language strings to the latest changes; updated the POT file
-- fixed a possible malfunction in Tool Punch Gerber
-
-18.05.2020
-
-- fixed the PDF Tool when importing as Gerber objects
-- moved all the parsing out of the PDF Tool to a new file ParsePDF in the flatcamParsers folder
-- trying to fix the pixmap load crash when running a FlatCAMScript
-- made the workspace label in the status bar clickable and also added a status bar message on status toggle for workspace
-- modified the GUI for Film and Panelize Tools
-- moved some of the GUI related methods from FlatCAMApp.App to the flatcamGUI.MainGUI class
-- moved Shortcuts Tab creation in it's own class
-- renamed classes to have shorter names and grouped
-- removed reference to postprocessors and replaced it with preprocessors
-- more refactoring class names
-- moved some of the methods from the App class to the ObjectCollection class
-- moved all the new_object related methods in their own class AppObjects.AppObject
-- more refactoring; solved some issues introduced by the refactoring
-- solved a circular import
-- updated the language translation files to the latest changes (no translation)
-- working on a new Tool: Etch Compensation Tool -> installed the tool and created the GUI and class template
-- moved more methods out of App_Main class
-- added confirmation messages for toggle of HUD, Grid, Grid Snap, Axis
-- added icon in status bar for HUD; clicking on it will toggle the HUD (heads up display)
-- fixes due of recent changes
-- fixed issue #417
-
-17.05.2020
-
-- added new FlatCAM Tool: Corner Markers Tool which will add line markers in the selected corners of the bounding box of the targeted Gerber object
-- added a menu entry in Menu -> View for Toggle HUD
-- solved the issue with the GUI in the Notebook being expanded too much in width due of the FCDoubleSpinner and FCSpinner sizeHint by setting the sizePolicy to Ignored value
-- fixed the workspace being always A4
-- added a label in the status bar to show if the workplace is active and what size it is
-- now the Edit command (either from Menu Edit ->Edit Object) or through the shortcut key (E key) or project tab context menu works also for the CNCJob objects (will open a text Editor with the GCode)
-- fixed the object collection methods that return a list of objects or names of objects such that they have a parameter now to allow adding to those lists (or not) for the objects of type Script or Document. Thus fixing some of the Tcl commands such Set Origin
-- reverted the previous changes to object collection; it is better to create empty methods in FlatCAMScript and FlatCAMDocument objects
-
-16.05.2020
-
-- worked on the NCC Tool; added a new clear method named 'Combo' which will go through all methods until the clear is done
-- added a Preferences parameter for font size used in HUD
-
-13.05.2020
-
-- updated the French translation strings, made by @micmac (Michel)
-
-12.05.2020
-
-- fixed recent issues introduced in Tcl command Drillcncjob
-- updated the Cncjob to use the 'endxy' parameter which dictates the x,y position at the end of the job
-- now the Tcl commands Drillcncjob and Cncjob can use the toolchangexy and endxy parameters with or without parenthesis (but no spaces allowed)
-- modified the Tcl command Paint "single" parameter. Now it's value is a tuple with the x,y coordinates of the single polygon to be painted.
-- the HUD display state is now persistent between app restarts
-- updated the Distance Tool such that the right click of the mouse will cancel the tool unless it was a panning move
-- modified the PlotCanvasLegacy to decide if there is a mouse drag based on the distance between the press event position and the release event position. If the distance is smaller than a delta distance then it is not a drag move.
-
-11.05.2020
-
-- removed the labels in status bar that display X,Y positions and replaced it with a HUD display on canvas (combo key SHIFT+H) will toggle the display of the HUD
-- made the HUD work in Legacy2D mode
-- fixed situation when the mouse cursor is outside of the canvas and no therefore returning None values
-- remade the Snap Toolbar presence; now it is always active and situated in the Status Bar
-- Snap Toolbar is now visible in Fullscreen
-- in Fullscreen now the Notebook is available but it will be hidden on Fullscreen launch
-- fixed some minor issues (in the HUD added a separating line, missing an icon in toolbars on first launch)
-- made sure that the corner snap buttons are shown only in Editors
-- changed the HUD color when using Dark theme 
-- fix issue in Legacy2D graphic mode where the snap function was not accessible when the PlotCanvasLegacy class was created
-- modified the HUD in Legacy2D when using Dark Theme to use different colors
-- modified how the graphic engine change act in Preferences: now only by clicking Apply(or Save) the change will happen. And there is also a message asking for confirmation
-- re-added the position labels in the status bar; they will be useful if HUD is Off (Altium does the same :) so learn from the best)
-- fixed the Tcl command Cncjob: there was a problem reported as issue #416. The command did not work due of the dpp parameter
-- modified the Tcl command Cncjob such that if some of the parameters are not used then the default values will be used (set with set_sys)
-- modified the Tcl command Drillcncjob to use the defaults when some of the parameters are not used
-
-10.05.2020
-
-- fixed the problem with using comma as decimal separator in Grid Snap fields
-
 9.05.2020
 
 - modified the GUI for Exclusion areas; now the shapes are displayed in a Table where they can be selected and deleted. Modification applied for Geometry Objects only (for now).
@@ -285,7 +62,7 @@ CHANGELOG for FlatCAM beta
 2.05.2020
 
 - changed the icons for the grid snap in the status bar
-- moved some of the methods from FlatCAMApp.App to flatcamGUI.MainGUI class
+- moved some of the methods from FlatCAMApp.App to flatcamGUI.FlatCAMGUI class
 - fixed bug in Gerber Editor in which the units conversion wasn't calculated correct
 - fixed bug in Gerber Editor in which the QThread that is started on object edit was not stopped at clean up stage
 - fixed bug in Gerber Editor that kept all the apertures (including the geometry) of a previously edited object that was not saved after edit
@@ -1553,7 +1330,7 @@ CHANGELOG for FlatCAM beta
 - optimized Rules Check Tool so it runs faster when doing Copper 2 Copper rule
 - small GUI changes in Optimal Tool and in Film Tool
 - some PEP8 corrections
-- some code annotations to make it easier to navigate in the MainGUI.py
+- some code annotations to make it easier to navigate in the FlatCAMGUI.py
 - fixed exit FullScreen with Escape key
 - added a new menu category in the MenuBar named 'Objects'. It will hold the objects found in the Project tab. Useful when working in FullScreen
 - disabled a log.debug in ObjectColection.get_by_name()
@@ -2992,7 +2769,7 @@ CHANGELOG for FlatCAM beta
 - fix for issue #262: when doing Edit-> Save & Close Editor on a Geometry that is not generated through first entering into an Editor, the geometry disappear
 - finished preparing for internationalization for the files: camlib and objectCollection
 - fixed tools shortcuts not working anymore due of the new toggle parameter for the .run().
-- finished preparing for internationalization for the files: FlatCAMEditor, MainGUI
+- finished preparing for internationalization for the files: FlatCAMEditor, FlatCAMGUI
 - finished preparing for internationalization for the files: FlatCAMObj, ObjectUI
 - sorted the languages in the Preferences combobox
 
@@ -3314,7 +3091,7 @@ CHANGELOG for FlatCAM beta
 - fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None
 - fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots.
 - changed the messages for Units Conversion
-- all key shortcuts work across the entire application; moved all the shortcuts definitions in MainGUI.keyPressEvent()
+- all key shortcuts work across the entire application; moved all the shortcuts definitions in FlatCAMGUI.keyPressEvent()
 - renamed the theme to layout because it is really a layout change
 - added plot kind for CNC Job in the App Preferences
 - combined the geocutout and cutout_any TCL commands - work in progress
@@ -3835,7 +3612,7 @@ For now they are used only for Excellon objects who do have toolchange events
 
 - fixed a reported bug generated by a typo for feedrate_z object in camlib.py. Because of that, the project could not be saved.
 - fixed a G01 usage (should be G1) in Marlin preprocessor.
-- changed the position of the Tool Dia entry in the Object UI and in MainGUI
+- changed the position of the Tool Dia entry in the Object UI and in FlatCAMGUI
 - fixed issues in the installer
 
 30.10.2018
@@ -4395,7 +4172,7 @@ still copper leftovers.
 - modified generate_milling method which had issues from the Python3 port (it could not sort the tools due of dict to dict comparison no longer possible).
 - modified the 'default' preprocessor in order to include a space between the value of Xcoord and the following Y
 - made optional the using of threads for the milling command; by default it is OFF (False) because in the current configuration it creates issues when it is using threads
-- modified the Panelize function and Tcl command Panelize. It was having issues due to multithreading (kept trying to modify a dictionary in redraw() method)and automatically selecting the last created object (feature introduced by me). I've added a parameter to the app_obj.new_object method, named autoselected (by default it is True) and in the panelize method I initialized it with False.
+- modified the Panelize function and Tcl command Panelize. It was having issues due to multithreading (kept trying to modify a dictionary in redraw() method)and automatically selecting the last created object (feature introduced by me). I've added a parameter to the new_object method, named autoselected (by default it is True) and in the panelize method I initialized it with False.
 By initializing the plot parameter with False for the temporary objects, I have increased dramatically the  generation speed of the panel because now the temporary object are no longer ploted which consumed time.
 - replaced log.warn() with log.warning() in camlib.py. Reason: deprecated
 - fixed the issue that the "Defaults" button was having no effect when clicked and Options Combo was in Project Options

+ 2 - 2
FlatCAM.py

@@ -3,8 +3,8 @@ import os
 
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings, Qt
-from App_Main import App
-from AppGUI import VisPyPatches
+from FlatCAMApp import App
+from flatcamGUI import VisPyPatches
 
 from multiprocessing import freeze_support
 # import copyreg

ファイルの差分が大きいため隠しています
+ 628 - 173
FlatCAMApp.py


+ 6 - 6
Bookmark.py → FlatCAMBookmark.py

@@ -1,5 +1,5 @@
 from PyQt5 import QtGui, QtCore, QtWidgets
-from AppGUI.GUIElements import FCTable, FCEntry, FCButton, FCFileSaveDialog
+from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCFileSaveDialog
 
 import sys
 import webbrowser
@@ -7,7 +7,7 @@ import webbrowser
 from copy import deepcopy
 from datetime import datetime
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -287,12 +287,12 @@ class BookmarkManager(QtWidgets.QWidget):
         date = date.replace(' ', '_')
 
         filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Bookmarks"),
-                                                           directory='{l_save}/{n}_{date}'.format(
+        filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export FlatCAM Bookmarks"),
+                                                           directory='{l_save}/FlatCAM_{n}_{date}'.format(
                                                                 l_save=str(self.app.get_last_save_folder()),
                                                                 n=_("Bookmarks"),
                                                                 date=date),
-                                                           ext_filter=filter__)
+                                                           filter=filter__)
 
         filename = str(filename)
 
@@ -334,7 +334,7 @@ class BookmarkManager(QtWidgets.QWidget):
         self.app.log.debug("on_import_bookmarks()")
 
         filter_ = "Text File (*.txt);;All Files (*.*)"
-        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import Bookmarks"), filter=filter_)
+        filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Bookmarks"), filter=filter_)
 
         filename = str(filename)
 

+ 63 - 344
Common.py → FlatCAMCommon.py

@@ -12,18 +12,15 @@
 # ##########################################################
 from PyQt5 import QtCore
 
-from shapely.geometry import Polygon, Point, LineString
-from shapely.ops import unary_union
+from shapely.geometry import Polygon, MultiPolygon
 
-from AppGUI.VisPyVisuals import ShapeCollection
-from AppTool import AppTool
-
-from copy import deepcopy
+from flatcamGUI.VisPyVisuals import ShapeCollection
+from FlatCAMTool import FlatCAMTool
 
 import numpy as np
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -32,9 +29,7 @@ if '_' not in builtins.__dict__:
 
 
 class GracefulException(Exception):
-    """
-    Graceful Exception raised when the user is requesting to cancel the current threaded task
-    """
+    # Graceful Exception raised when the user is requesting to cancel the current threaded task
     def __init__(self):
         super().__init__()
 
@@ -109,11 +104,8 @@ def color_variant(hex_color, bright_factor=1):
     Takes a color in HEX format #FF00FF and produces a lighter or darker variant
 
     :param hex_color:           color to change
-    :type hex_color:            str
-    :param bright_factor:       factor to change the color brightness [0 ... 1]
-    :type bright_factor:        float
-    :return:                    Modified color
-    :rtype:                     str
+    :param bright_factor:   factor to change the color brightness [0 ... 1]
+    :return:                    modified color
     """
 
     if len(hex_color) != 7:
@@ -138,9 +130,7 @@ def color_variant(hex_color, bright_factor=1):
 
 
 class ExclusionAreas(QtCore.QObject):
-    """
-    Functionality for adding Exclusion Areas for the Excellon and Geometry FlatCAM Objects
-    """
+
     e_shape_modified = QtCore.pyqtSignal()
 
     def __init__(self, app):
@@ -156,7 +146,7 @@ class ExclusionAreas(QtCore.QObject):
             except AttributeError:
                 self.exclusion_shapes = None
         else:
-            from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
             self.exclusion_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name="exclusion")
 
         # Event signals disconnect id holders
@@ -177,8 +167,8 @@ class ExclusionAreas(QtCore.QObject):
         {
             "obj_type":   string ("excellon" or "geometry")   <- self.obj_type
             "shape":      Shapely polygon
-            "strategy":   string ("over" or "around")         <- self.strategy_button
-            "overz":      float                               <- self.over_z_button
+            "strategy":   string ("over" or "around")         <- self.strategy
+            "overz":      float                               <- self.over_z
         }
         '''
         self.exclusion_areas_storage = []
@@ -188,9 +178,9 @@ class ExclusionAreas(QtCore.QObject):
         self.solid_geometry = []
         self.obj_type = None
 
-        self.shape_type_button = None
-        self.over_z_button = None
-        self.strategy_button = None
+        self.shape_type = 'square'  # TODO use the self.app.defaults when made general (not in Geo object Pref UI)
+        self.over_z = 0.1
+        self.strategy = None
         self.cnc_button = None
 
     def on_add_area_click(self, shape_button, overz_button, strategy_radio, cnc_button, solid_geo, obj_type):
@@ -198,22 +188,21 @@ class ExclusionAreas(QtCore.QObject):
 
         :param shape_button:    a FCButton that has the value for the shape
         :param overz_button:    a FCDoubleSpinner that holds the Over Z value
-        :param strategy_radio:  a RadioSet button with the strategy_button value
+        :param strategy_radio:  a RadioSet button with the strategy value
         :param cnc_button:      a FCButton in Object UI that when clicked the CNCJob is created
                                 We have a reference here so we can change the color signifying that exclusion areas are
                                 available.
         :param solid_geo:       reference to the object solid geometry for which we add exclusion areas
-        :param obj_type:        Type of FlatCAM object that called this method. String: "excellon" or "geometry"
-        :type obj_type:         str
-        :return:                None
+        :param obj_type:        Type of FlatCAM object that called this method
+        :type obj_type:         String: "excellon" or "geometry"
+        :return:
         """
         self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
         self.app.call_source = 'geometry'
 
-        self.shape_type_button = shape_button
-
-        self.over_z_button = overz_button
-        self.strategy_button = strategy_radio
+        self.shape_type = shape_button.get_value()
+        self.over_z = overz_button.get_value()
+        self.strategy = strategy_radio.get_value()
         self.cnc_button = cnc_button
 
         self.solid_geometry = solid_geo
@@ -234,14 +223,6 @@ class ExclusionAreas(QtCore.QObject):
 
     # To be called after clicking on the plot.
     def on_mouse_release(self, event):
-        """
-        Called on mouse click release.
-
-        :param event:   Mouse event
-        :type event:
-        :return:        None
-        :rtype:
-        """
         if self.app.is_legacy is False:
             event_pos = event.pos
             # event_is_dragging = event.is_dragging
@@ -259,11 +240,11 @@ class ExclusionAreas(QtCore.QObject):
 
         x1, y1 = curr_pos[0], curr_pos[1]
 
-        # shape_type_button = self.ui.area_shape_radio.get_value()
+        # shape_type = self.ui.area_shape_radio.get_value()
 
         # do clear area only for left mouse clicks
         if event.button == 1:
-            if self.shape_type_button.get_value() == "square":
+            if self.shape_type == "square":
                 if self.first_click is False:
                     self.first_click = True
                     self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the area."))
@@ -287,14 +268,14 @@ class ExclusionAreas(QtCore.QObject):
                     # {
                     #     "obj_type":   string("excellon" or "geometry") < - self.obj_type
                     #     "shape":      Shapely polygon
-                    #     "strategy_button":   string("over" or "around") < - self.strategy_button
-                    #     "overz":      float < - self.over_z_button
+                    #     "strategy":   string("over" or "around") < - self.strategy
+                    #     "overz":      float < - self.over_z
                     # }
                     new_el = {
                         "obj_type":     self.obj_type,
                         "shape":        new_rectangle,
-                        "strategy":     self.strategy_button.get_value(),
-                        "overz":        self.over_z_button.get_value()
+                        "strategy":     self.strategy,
+                        "overz":        self.over_z
                     }
                     self.exclusion_areas_storage.append(new_el)
 
@@ -306,7 +287,7 @@ class ExclusionAreas(QtCore.QObject):
                         face_color = "#FF7400BF"
 
                     # add a temporary shape on canvas
-                    AppTool.draw_tool_selection_shape(
+                    FlatCAMTool.draw_tool_selection_shape(
                         self, old_coords=(x0, y0), coords=(x1, y1),
                         color=color,
                         face_color=face_color,
@@ -324,7 +305,7 @@ class ExclusionAreas(QtCore.QObject):
                 return ""
         elif event.button == right_button and self.mouse_is_dragging is False:
 
-            shape_type = self.shape_type_button.get_value()
+            shape_type = self.shape_type
 
             if shape_type == "square":
                 self.first_click = False
@@ -341,23 +322,21 @@ class ExclusionAreas(QtCore.QObject):
 
                     # we need to add a Polygon and a Polygon can be made only from at least 3 points
                     if len(self.points) > 2:
-                        AppTool.delete_moving_selection_shape(self)
+                        FlatCAMTool.delete_moving_selection_shape(self)
                         pol = Polygon(self.points)
                         # do not add invalid polygons even if they are drawn by utility geometry
                         if pol.is_valid:
-                            """
-                            {
-                                "obj_type":   string("excellon" or "geometry") < - self.obj_type
-                                "shape":      Shapely polygon
-                                "strategy":   string("over" or "around") < - self.strategy_button
-                                "overz":      float < - self.over_z_button
-                            }
-                            """
+                            # {
+                            #     "obj_type":   string("excellon" or "geometry") < - self.obj_type
+                            #     "shape":      Shapely polygon
+                            #     "strategy":   string("over" or "around") < - self.strategy
+                            #     "overz":      float < - self.over_z
+                            # }
                             new_el = {
                                 "obj_type": self.obj_type,
                                 "shape": pol,
-                                "strategy": self.strategy_button.get_value(),
-                                "overz": self.over_z_button.get_value()
+                                "strategy": self.strategy,
+                                "overz": self.over_z
                             }
                             self.exclusion_areas_storage.append(new_el)
 
@@ -368,7 +347,7 @@ class ExclusionAreas(QtCore.QObject):
                                 color = "#098a8f"
                                 face_color = "#FF7400BF"
 
-                            AppTool.draw_selection_shape_polygon(
+                            FlatCAMTool.draw_selection_shape_polygon(
                                 self, points=self.points,
                                 color=color,
                                 face_color=face_color,
@@ -380,7 +359,7 @@ class ExclusionAreas(QtCore.QObject):
                     self.poly_drawn = False
                     return
 
-            # AppTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
+            # FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
 
             if self.app.is_legacy is False:
                 self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
@@ -403,11 +382,11 @@ class ExclusionAreas(QtCore.QObject):
             if len(self.exclusion_areas_storage) == 0:
                 return
 
-            # since the exclusion areas should apply to all objects in the app collection, this check is limited to
-            # only the current object therefore it will not guarantee success
-            self.app.inform.emit("%s" % _("Exclusion areas added. Checking overlap with the object geometry ..."))
+            self.app.inform.emit(
+                "[success] %s" % _("Exclusion areas added. Checking overlap with the object geometry ..."))
+
             for el in self.exclusion_areas_storage:
-                if el["shape"].intersects(unary_union(self.solid_geometry)):
+                if el["shape"].intersects(MultiPolygon(self.solid_geometry)):
                     self.on_clear_area_click()
                     self.app.inform.emit(
                         "[ERROR_NOTCL] %s" % _("Failed. Exclusion areas intersects the object geometry ..."))
@@ -427,15 +406,10 @@ class ExclusionAreas(QtCore.QObject):
             )
 
             self.e_shape_modified.emit()
+            for k in self.exclusion_areas_storage:
+                print(k)
 
     def area_disconnect(self):
-        """
-        Will do the cleanup. Will disconnect the mouse events for the custom handlers in this class and initialize
-        certain class attributes.
-
-        :return:    None
-        :rtype:
-        """
         if self.app.is_legacy is False:
             self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
             self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
@@ -454,22 +428,15 @@ class ExclusionAreas(QtCore.QObject):
         self.poly_drawn = False
         self.exclusion_areas_storage = []
 
-        AppTool.delete_moving_selection_shape(self)
-        # AppTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
+        FlatCAMTool.delete_moving_selection_shape(self)
+        # FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
 
         self.app.call_source = "app"
         self.app.inform.emit("[WARNING_NOTCL] %s" % _("Cancelled. Area exclusion drawing was interrupted."))
 
+    # called on mouse move
     def on_mouse_move(self, event):
-        """
-        Called on mouse move
-
-        :param event:   mouse event
-        :type event:
-        :return:        None
-        :rtype:
-        """
-        shape_type = self.shape_type_button.get_value()
+        shape_type = self.shape_type
 
         if self.app.is_legacy is False:
             event_pos = event.pos
@@ -499,20 +466,15 @@ class ExclusionAreas(QtCore.QObject):
                                          size=self.app.defaults["global_cursor_size"])
 
         # update the positions on status bar
+        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
         if self.cursor_pos is None:
             self.cursor_pos = (0, 0)
 
         self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
         self.app.dy = curr_pos[1] - float(self.cursor_pos[1])
-        self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                           "<b>Y</b>: %.4f&nbsp;" % (curr_pos[0], curr_pos[1]))
-        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
-
-        units = self.app.defaults["units"].lower()
-        self.app.plotcanvas.text_hud.text = \
-            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
-                self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
+        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))
 
         if self.obj_type == 'excellon':
             color = "#FF7400"
@@ -531,20 +493,14 @@ class ExclusionAreas(QtCore.QObject):
                                                      face_color=face_color,
                                                      coords=(curr_pos[0], curr_pos[1]))
         else:
-            AppTool.delete_moving_selection_shape(self)
-            AppTool.draw_moving_selection_shape_poly(
+            FlatCAMTool.delete_moving_selection_shape(self)
+            FlatCAMTool.draw_moving_selection_shape_poly(
                 self, points=self.points,
                 color=color,
                 face_color=face_color,
                 data=(curr_pos[0], curr_pos[1]))
 
     def on_clear_area_click(self):
-        """
-        Slot for clicking the button for Deleting all the Exclusion areas.
-
-        :return:    None
-        :rtype:
-        """
         self.clear_shapes()
 
         # restore the default StyleSheet
@@ -559,28 +515,21 @@ class ExclusionAreas(QtCore.QObject):
         self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object."))
 
     def clear_shapes(self):
-        """
-        Will delete all the Exclusion areas; will delete on canvas any possible selection box for the Exclusion areas.
-
-        :return:    None
-        :rtype:
-        """
         self.exclusion_areas_storage.clear()
-        AppTool.delete_moving_selection_shape(self)
+        FlatCAMTool.delete_moving_selection_shape(self)
         self.app.delete_selection_shape()
-        AppTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
-        self.app.inform.emit('%s' % _("All exclusion zones deleted."))
+        FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
+        self.app.inform.emit('[success] %s' % _("All exclusion zones deleted."))
 
     def delete_sel_shapes(self, idxs):
         """
 
-        :param idxs:    list of indexes in self.exclusion_areas_storage list to be deleted
-        :type idxs:     list
-        :return:        None
+        :param idxs: list of indexes in self.exclusion_areas_storage list to be deleted
+        :return:
         """
 
         # delete all plotted shapes
-        AppTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
+        FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
 
         # delete shapes
         for idx in sorted(idxs, reverse=True):
@@ -618,234 +567,4 @@ class ExclusionAreas(QtCore.QObject):
                                             """)
             self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object."))
 
-            self.app.inform.emit('%s' % _("All exclusion zones deleted."))
-
-    def travel_coordinates(self, start_point, end_point, tooldia):
-        """
-        WIll create a path the go around the exclusion areas on the shortest path when travelling (at a Z above the
-        material).
-
-        :param start_point:     X,Y coordinates for the start point of the travel line
-        :type start_point:      tuple
-        :param end_point:       X,Y coordinates for the destination point of the travel line
-        :type end_point:        tuple
-        :param tooldia:         THe tool diameter used and which generates the travel lines
-        :type tooldia           float
-        :return:                A list of x,y tuples that describe the avoiding path
-        :rtype:                 list
-        """
-
-        ret_list = []
-
-        # Travel lines: rapids. Should not pass through Exclusion areas
-        travel_line = LineString([start_point, end_point])
-        origin_point = Point(start_point)
-
-        buffered_storage = []
-        # add a little something to the half diameter, to make sure that we really don't enter in the exclusion zones
-        buffered_distance = (tooldia / 2.0) + (0.1 if self.app.defaults['units'] == 'MM' else 0.00393701)
-
-        for area in self.exclusion_areas_storage:
-            new_area = deepcopy(area)
-            new_area['shape'] = area['shape'].buffer(buffered_distance, join_style=2)
-            buffered_storage.append(new_area)
-
-        # sort the Exclusion areas from the closest to the start_point to the farthest
-        tmp = []
-        for area in buffered_storage:
-            dist = Point(start_point).distance(area['shape'])
-            tmp.append((dist, area))
-        tmp.sort(key=lambda k: k[0])
-
-        sorted_area_storage = [k[1] for k in tmp]
-
-        # process the ordered exclusion areas list
-        for area in sorted_area_storage:
-            outline = area['shape'].exterior
-            if travel_line.intersects(outline):
-                intersection_pts = travel_line.intersection(outline)
-
-                if isinstance(intersection_pts, Point):
-                    # it's just a touch, continue
-                    continue
-
-                entry_pt = nearest_point(origin_point, intersection_pts)
-                exit_pt = farthest_point(origin_point, intersection_pts)
-
-                if area['strategy'] == 'around':
-                    full_vertex_points = [Point(x) for x in list(outline.coords)]
-
-                    # the last coordinate in outline, a LinearRing, is the closing one
-                    # therefore a duplicate of the first one; discard it
-                    vertex_points = full_vertex_points[:-1]
-
-                    # dist_from_entry = [(entry_pt.distance(vt), vertex_points.index(vt)) for vt in vertex_points]
-                    # closest_point_entry = nsmallest(1, dist_from_entry, key=lambda x: x[0])
-                    # start_idx = closest_point_entry[0][1]
-                    #
-                    # dist_from_exit = [(exit_pt.distance(vt), vertex_points.index(vt)) for vt in vertex_points]
-                    # closest_point_exit = nsmallest(1, dist_from_exit, key=lambda x: x[0])
-                    # end_idx = closest_point_exit[0][1]
-
-                    # pts_line_entry = None
-                    # pts_line_exit = None
-                    # for i in range(len(full_vertex_points)):
-                    #     try:
-                    #         line = LineString(
-                    #             [
-                    #                 (full_vertex_points[i].x, full_vertex_points[i].y),
-                    #                 (full_vertex_points[i + 1].x, full_vertex_points[i + 1].y)
-                    #             ]
-                    #         )
-                    #     except IndexError:
-                    #         continue
-                    #
-                    #     if entry_pt.within(line) or entry_pt.equals(Point(line.coords[0])) or \
-                    #             entry_pt.equals(Point(line.coords[1])):
-                    #         pts_line_entry = [Point(x) for x in line.coords]
-                    #
-                    #     if exit_pt.within(line) or exit_pt.equals(Point(line.coords[0])) or \
-                    #             exit_pt.equals(Point(line.coords[1])):
-                    #         pts_line_exit = [Point(x) for x in line.coords]
-                    #
-                    # closest_point_entry = nearest_point(entry_pt, pts_line_entry)
-                    # start_idx = vertex_points.index(closest_point_entry)
-                    #
-                    # closest_point_exit = nearest_point(exit_pt, pts_line_exit)
-                    # end_idx = vertex_points.index(closest_point_exit)
-
-                    # find all vertexes for which a line from start_point does not cross the Exclusion area polygon
-                    # the same for end_point
-                    # we don't need closest points for which the path leads to crosses of the Exclusion area
-
-                    close_start_points = []
-                    close_end_points = []
-                    for i in range(len(vertex_points)):
-                        try:
-                            start_line = LineString(
-                                [
-                                    start_point,
-                                    (vertex_points[i].x, vertex_points[i].y)
-                                ]
-                            )
-                            end_line = LineString(
-                                [
-                                    end_point,
-                                    (vertex_points[i].x, vertex_points[i].y)
-                                ]
-                            )
-                        except IndexError:
-                            continue
-
-                        if not start_line.crosses(area['shape']):
-                            close_start_points.append(vertex_points[i])
-                        if not end_line.crosses(area['shape']):
-                            close_end_points.append(vertex_points[i])
-
-                    closest_point_entry = nearest_point(entry_pt, close_start_points)
-                    closest_point_exit = nearest_point(exit_pt, close_end_points)
-
-                    start_idx = vertex_points.index(closest_point_entry)
-                    end_idx = vertex_points.index(closest_point_exit)
-
-                    # calculate possible paths: one clockwise the other counterclockwise on the exterior of the
-                    # exclusion area outline (Polygon.exterior)
-                    vp_len = len(vertex_points)
-                    if end_idx > start_idx:
-                        path_1 = vertex_points[start_idx:(end_idx + 1)]
-                        path_2 = [vertex_points[start_idx]]
-                        idx = start_idx
-                        for __ in range(vp_len):
-                            idx = idx - 1 if idx > 0 else (vp_len - 1)
-                            path_2.append(vertex_points[idx])
-                            if idx == end_idx:
-                                break
-                    else:
-                        path_1 = vertex_points[end_idx:(start_idx + 1)]
-                        path_2 = [vertex_points[end_idx]]
-                        idx = end_idx
-                        for __ in range(vp_len):
-                            idx = idx - 1 if idx > 0 else (vp_len - 1)
-                            path_2.append(vertex_points[idx])
-                            if idx == start_idx:
-                                break
-                        path_1.reverse()
-                        path_2.reverse()
-
-                    # choose the one with the lesser length
-                    length_path_1 = 0
-                    for i in range(len(path_1)):
-                        try:
-                            length_path_1 += path_1[i].distance(path_1[i + 1])
-                        except IndexError:
-                            pass
-
-                    length_path_2 = 0
-                    for i in range(len(path_2)):
-                        try:
-                            length_path_2 += path_2[i].distance(path_2[i + 1])
-                        except IndexError:
-                            pass
-
-                    path = path_1 if length_path_1 < length_path_2 else path_2
-
-                    # transform the list of Points into a list of Points coordinates
-                    path_coords = [[None, (p.x, p.y)] for p in path]
-                    ret_list += path_coords
-
-                else:
-                    path_coords = [[float(area['overz']), (entry_pt.x, entry_pt.y)], [None, (exit_pt.x, exit_pt.y)]]
-                    ret_list += path_coords
-
-                # create a new LineString to test again for possible other Exclusion zones
-                last_pt_in_path = path_coords[-1][1]
-                travel_line = LineString([last_pt_in_path, end_point])
-
-        ret_list.append([None, end_point])
-        return ret_list
-
-
-def farthest_point(origin, points_list):
-    """
-    Calculate the farthest Point in a list from another Point
-
-    :param origin:      Reference Point
-    :type origin:       Point
-    :param points_list: List of Points or a MultiPoint
-    :type points_list:  list
-    :return:            Farthest Point
-    :rtype:             Point
-    """
-    old_dist = 0
-    fartherst_pt = None
-
-    for pt in points_list:
-        dist = abs(origin.distance(pt))
-        if dist >= old_dist:
-            fartherst_pt = pt
-            old_dist = dist
-
-    return fartherst_pt
-
-
-def nearest_point(origin, points_list):
-    """
-    Calculate the nearest Point in a list from another Point
-
-    :param origin:      Reference Point
-    :type origin:       Point
-    :param points_list: List of Points or a MultiPoint
-    :type points_list:  list
-    :return:            Nearest Point
-    :rtype:             Point
-    """
-    old_dist = np.Inf
-    nearest_pt = None
-
-    for pt in points_list:
-        dist = abs(origin.distance(pt))
-        if dist <= old_dist:
-            nearest_pt = pt
-            old_dist = dist
-
-    return nearest_pt
+            self.app.inform.emit('[success] %s' % _("All exclusion zones deleted."))

+ 18 - 224
AppDatabase.py → FlatCAMDB.py

@@ -1,5 +1,5 @@
 from PyQt5 import QtGui, QtCore, QtWidgets
-from AppGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
+from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
     FCTree, RadioSet, FCFileSaveDialog
 from camlib import to_dict
 
@@ -8,10 +8,8 @@ import json
 
 from copy import deepcopy
 from datetime import datetime
-import math
-
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -119,7 +117,7 @@ class ToolsDB(QtWidgets.QWidget):
         )
         self.buttons_box.addWidget(import_db_btn)
 
-        self.add_tool_from_db = FCButton(_("Transfer the Tool"))
+        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
         self.add_tool_from_db.setToolTip(
             _("Add a new tool in the Tools Table of the\n"
               "active Geometry object after selecting a tool\n"
@@ -315,7 +313,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 Tools DB from"), filename))
+        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
 
         self.build_db_ui()
 
@@ -657,7 +655,7 @@ class ToolsDB(QtWidgets.QWidget):
                                                                l_save=str(self.app.get_last_save_folder()),
                                                                n=_("Tools_Database"),
                                                                date=date),
-                                                           ext_filter=filter__)
+                                                           filter=filter__)
 
         filename = str(filename)
 
@@ -726,7 +724,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 Tools DB from"), filename))
+            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
             self.build_db_ui()
             self.callback_on_edited()
 
@@ -1032,7 +1030,6 @@ 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
@@ -1045,7 +1042,6 @@ 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
@@ -1058,24 +1054,10 @@ 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)
@@ -1085,7 +1067,6 @@ 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)
@@ -1497,7 +1478,7 @@ class ToolsDB2(QtWidgets.QWidget):
 
         self.ncc_method_combo = FCComboBox()
         self.ncc_method_combo.addItems(
-            [_("Standard"), _("Seed"), _("Lines"), _("Combo")]
+            [_("Standard"), _("Seed"), _("Lines")]
         )
         self.ncc_method_combo.setObjectName("gdb_n_method")
 
@@ -1640,101 +1621,6 @@ 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
@@ -1792,19 +1678,12 @@ class ToolsDB2(QtWidgets.QWidget):
         )
         self.buttons_box.addWidget(self.save_db_btn)
 
-        self.add_tool_from_db = FCButton(_("Transfer the Tool"))
+        self.add_tool_from_db = FCButton(_("Add Tool from Tools DB"))
         self.add_tool_from_db.setToolTip(
-            _("Insert a new tool in the Tools Table of the\n"
-              "object/application tool after selecting a tool\n"
+            _("Add a new tool in the Tools Table of the\n"
+              "active Geometry object after selecting a tool\n"
               "in the Tools Database.")
         )
-        self.add_tool_from_db.setStyleSheet("""
-                                            QPushButton
-                                            {
-                                                font-weight: bold;
-                                                color: green;
-                                            }
-                                            """)
         self.add_tool_from_db.hide()
 
         self.cancel_tool_from_db = FCButton(_("Cancel"))
@@ -1814,7 +1693,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()
 
         # ##############################################################################
         # ##############################################################################
@@ -1864,13 +1743,6 @@ 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 = {
@@ -1915,13 +1787,6 @@ 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
@@ -2022,7 +1887,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:
@@ -2041,7 +1906,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 Tools DB from"), filename))
+        self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
 
         self.build_db_ui()
 
@@ -2074,23 +1939,21 @@ class ToolsDB2(QtWidgets.QWidget):
             if self.db_tool_dict:
                 self.storage_to_form(self.db_tool_dict['1'])
 
-                # Enable AppGUI
+                # Enable GUI
                 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 AppGUI
+                # Disable GUI
                 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)])
 
@@ -2143,27 +2006,10 @@ 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_name
+        dict_elem['name'] = 'new_tool'
         if type(self.app.defaults["geometry_cnctooldia"]) == float:
             dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
         else:
@@ -2271,7 +2117,7 @@ class ToolsDB2(QtWidgets.QWidget):
                                                                 l_save=str(self.app.get_last_save_folder()),
                                                                 n=_("Tools_Database"),
                                                                 date=date),
-                                                           ext_filter=filter__)
+                                                           filter=filter__)
 
         filename = str(filename)
 
@@ -2340,7 +2186,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 Tools DB from"), filename))
+            self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename))
             self.build_db_ui()
             self.update_storage()
 
@@ -2372,18 +2218,6 @@ 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()
@@ -2413,40 +2247,12 @@ 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]
 
@@ -2592,18 +2398,6 @@ 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):

+ 0 - 0
AppPool.py → FlatCAMPool.py


+ 4 - 4
AppPreProcessor.py → FlatCAMPostProc.py

@@ -19,10 +19,10 @@ log = logging.getLogger('base')
 preprocessors = {}
 
 
-class ABCPreProcRegister(ABCMeta):
+class ABCPostProcRegister(ABCMeta):
     # handles preprocessors registration on instantiation
     def __new__(cls, clsname, bases, attrs):
-        newclass = super(ABCPreProcRegister, cls).__new__(cls, clsname, bases, attrs)
+        newclass = super(ABCPostProcRegister, cls).__new__(cls, clsname, bases, attrs)
         if object not in bases:
             if newclass.__name__ in preprocessors:
                 log.warning('Preprocessor %s has been overriden' % newclass.__name__)
@@ -30,7 +30,7 @@ class ABCPreProcRegister(ABCMeta):
         return newclass
 
 
-class PreProc(object, metaclass=ABCPreProcRegister):
+class FlatCAMPostProc(object, metaclass=ABCPostProcRegister):
     @abstractmethod
     def start_code(self, p):
         pass
@@ -76,7 +76,7 @@ class PreProc(object, metaclass=ABCPreProcRegister):
         pass
 
 
-class AppPreProcTools(object, metaclass=ABCPreProcRegister):
+class FlatCAMPostProc_Tools(object, metaclass=ABCPostProcRegister):
     @abstractmethod
     def start_code(self, p):
         pass

+ 2 - 2
AppProcess.py → FlatCAMProcess.py

@@ -6,12 +6,12 @@
 # MIT Licence                                              #
 # ##########################################################
 
-from AppGUI.GUIElements import FlatCAMActivityView
+from flatcamGUI.FlatCAMGUI import FlatCAMActivityView
 from PyQt5 import QtCore
 import weakref
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')

+ 19 - 22
AppTool.py → FlatCAMTool.py

@@ -6,12 +6,13 @@
 # MIT Licence                                              #
 # ########################################################## ##
 
-from PyQt5 import QtCore, QtWidgets
+from PyQt5 import QtGui, QtCore, QtWidgets, QtWidgets
+from PyQt5.QtCore import Qt
 
 from shapely.geometry import Polygon, LineString
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -19,23 +20,22 @@ if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
 
-class AppTool(QtWidgets.QWidget):
+class FlatCAMTool(QtWidgets.QWidget):
 
     toolName = "FlatCAM Generic Tool"
 
     def __init__(self, app, parent=None):
         """
 
-        :param app:         The application this tool will run in.
-        :type app:          App_Main.App
-        :param parent:      Qt Parent
-        :return:            AppTool
+        :param app: The application this tool will run in.
+        :type app: App
+        :param parent: Qt Parent
+        :return: FlatCAMTool
         """
-        QtWidgets.QWidget.__init__(self, parent)
-
         self.app = app
-        self.decimals = self.app.decimals
+        self.decimals = app.decimals
 
+        QtWidgets.QWidget.__init__(self, parent)
         # self.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
 
         self.layout = QtWidgets.QVBoxLayout()
@@ -87,10 +87,10 @@ class AppTool(QtWidgets.QWidget):
 
         if self.app.tool_tab_locked is True:
             return
-        # Remove anything else in the AppGUI
+        # Remove anything else in the GUI
         self.app.ui.tool_scroll_area.takeWidget()
 
-        # Put ourself in the AppGUI
+        # Put ourself in the GUI
         self.app.ui.tool_scroll_area.setWidget(self)
 
         # Switch notebook to tool page
@@ -277,25 +277,22 @@ class AppTool(QtWidgets.QWidget):
 
     def confirmation_message(self, accepted, minval, maxval):
         if accepted is False:
-            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
-                                                                                  self.decimals,
-                                                                                  minval,
-                                                                                  self.decimals,
-                                                                                  maxval), False)
+            self.app.inform.emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' %
+                                 (_("Edited value is out of range"), self.decimals, minval, self.decimals, maxval))
         else:
-            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
 
     def confirmation_message_int(self, accepted, minval, maxval):
         if accepted is False:
-            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
-                                            (_("Edited value is out of range"), minval, maxval), False)
+            self.app.inform.emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                 (_("Edited value is out of range"), minval, maxval))
         else:
-            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
 
     def sizeHint(self):
         """
         I've overloaded this just in case I will need to make changes in the future to enforce dimensions
         :return:
         """
-        default_hint_size = super(AppTool, self).sizeHint()
+        default_hint_size = super(FlatCAMTool, self).sizeHint()
         return QtCore.QSize(default_hint_size.width(), default_hint_size.height())

+ 0 - 4
AppTranslation.py → FlatCAMTranslation.py

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

+ 0 - 0
AppWorker.py → FlatCAMWorker.py


+ 1 - 1
AppWorkerStack.py → FlatCAMWorkerStack.py

@@ -1,5 +1,5 @@
 from PyQt5 import QtCore
-from AppWorker import Worker
+from FlatCAMWorker import Worker
 import multiprocessing
 
 

+ 0 - 195
Utils/vispy_example.py

@@ -1,195 +0,0 @@
-from PyQt5.QtGui import QPalette
-from PyQt5 import QtCore, QtWidgets
-
-import vispy.scene as scene
-from vispy.scene.visuals import Rectangle, Text
-from vispy.color import Color
-
-import sys
-
-
-class VisPyCanvas(scene.SceneCanvas):
-
-    def __init__(self, config=None):
-        super().__init__(config=config, keys=None)
-
-        self.unfreeze()
-        
-        # Colors used by the Scene
-        theme_color = Color('#FFFFFF')
-        tick_color = Color('#000000')
-        back_color = str(QPalette().color(QPalette.Window).name())
-        
-        # Central Widget Colors
-        self.central_widget.bgcolor = back_color
-        self.central_widget.border_color = back_color
-
-        self.grid_widget = self.central_widget.add_grid(margin=10)
-        self.grid_widget.spacing = 0
-        
-        # TOP Padding
-        top_padding = self.grid_widget.add_widget(row=0, col=0, col_span=2)
-        top_padding.height_max = 0
-
-        # RIGHT Padding
-        right_padding = self.grid_widget.add_widget(row=0, col=2, row_span=2)
-        right_padding.width_max = 0
-
-        # X Axis
-        self.xaxis = scene.AxisWidget(
-            orientation='bottom', axis_color=tick_color, text_color=tick_color,
-            font_size=8, axis_width=1,
-            anchors=['center', 'bottom']
-        )
-        self.xaxis.height_max = 30
-        self.grid_widget.add_widget(self.xaxis, row=2, col=1)
-
-        # Y Axis
-        self.yaxis = scene.AxisWidget(
-            orientation='left', axis_color=tick_color, text_color=tick_color, 
-            font_size=8, axis_width=1
-        )
-        self.yaxis.width_max = 55
-        self.grid_widget.add_widget(self.yaxis, row=1, col=0)
-
-        # View & Camera
-        self.view = self.grid_widget.add_view(row=1, col=1, border_color=tick_color,
-                                              bgcolor=theme_color)
-        self.view.camera = scene.PanZoomCamera(aspect=1, rect=(-25, -25, 150, 150))
-
-        self.xaxis.link_view(self.view)
-        self.yaxis.link_view(self.view)
-
-        self.grid = scene.GridLines(parent=self.view.scene, color='dimgray')
-        self.grid.set_gl_state(depth_test=False)
-
-        self.rect = Rectangle(center=(65,30), color=Color('#0000FF10'), border_color=Color('#0000FF10'),
-                              width=120, height=50, radius=[5, 5, 5, 5], parent=self.view)
-        self.rect.set_gl_state(depth_test=False)
-
-        self.text = Text('', parent=self.view, color='black', pos=(5, 30), method='gpu', anchor_x='left')
-        self.text.font_size = 8
-        self.text.text = 'Coordinates:\nX: %s\nY: %s' % ('0.0000', '0.0000')
-
-        self.freeze()
-
-        # self.measure_fps()
-
-
-class PlotCanvas(QtCore.QObject):
-
-    def __init__(self, container, my_app):
-        """
-        The constructor configures the VisPy figure that
-        will contain all plots, creates the base axes and connects
-        events to the plotting area.
-
-        :param container: The parent container in which to draw plots.
-        :rtype: PlotCanvas
-        """
-
-        super().__init__()
-        
-        # VisPyCanvas instance
-        self.vispy_canvas = VisPyCanvas()
-        
-        self.vispy_canvas.unfreeze()
-        
-        self.my_app = my_app
-        
-        # Parent container
-        self.container = container
-        
-        # <VisPyCanvas>
-        self.vispy_canvas.create_native()
-        self.vispy_canvas.native.setParent(self.my_app.ui)
-
-        # <QtCore.QObject>
-        self.container.addWidget(self.vispy_canvas.native)
-        
-        # add two Infinite Lines to act as markers for the X,Y axis
-        self.v_line = scene.visuals.InfiniteLine(
-            pos=0, color=(0.0, 0.0, 1.0, 0.3), vertical=True, 
-            parent=self.vispy_canvas.view.scene)
-
-        self.h_line = scene.visuals.InfiniteLine(
-            pos=0, color=(0.00, 0.0, 1.0, 0.3), vertical=False, 
-            parent=self.vispy_canvas.view.scene)
-        
-        self.vispy_canvas.freeze()
-    
-    def event_connect(self, event, callback):
-        getattr(self.vispy_canvas.events, event).connect(callback)
-        
-    def event_disconnect(self, event, callback):
-        getattr(self.vispy_canvas.events, event).disconnect(callback)
-    
-    def translate_coords(self, pos):
-        """
-        Translate pixels to canvas units.
-        """
-        tr = self.vispy_canvas.grid.get_transform('canvas', 'visual')
-        return tr.map(pos)
-        
-
-class MyGui(QtWidgets.QMainWindow):
-
-    def __init__(self):
-        super().__init__()
-
-        self.setWindowTitle("VisPy Test")
-
-        # add Menubar
-        self.menu = self.menuBar()
-        self.menufile = self.menu.addMenu("File")
-        self.menuedit = self.menu.addMenu("Edit")
-        self.menufhelp = self.menu.addMenu("Help")
-
-        # add a Toolbar
-        self.file_toolbar = QtWidgets.QToolBar("File Toolbar")
-        self.addToolBar(self.file_toolbar)
-        self.button = self.file_toolbar.addAction("Open")
-
-        # add Central Widget
-        self.c_widget = QtWidgets.QWidget()
-        self.central_layout = QtWidgets.QVBoxLayout()
-        self.c_widget.setLayout(self.central_layout)
-        self.setCentralWidget(self.c_widget)
-
-        # add InfoBar
-        # self.infobar = self.statusBar()
-        # self.position_label = QtWidgets.QLabel("Position:  X: 0.0000\tY: 0.0000")
-        # self.infobar.addWidget(self.position_label)
-
-
-class MyApp(QtCore.QObject):
-
-    def __init__(self):
-        super().__init__()
-        
-        self.ui = MyGui()
-        self.plot = PlotCanvas(container=self.ui.central_layout, my_app=self)
-        
-        self.ui.show()
-        
-        self.plot.event_connect(event="mouse_move", callback=self.on_mouse_move)
-    
-    def on_mouse_move(self, event):
-        cursor_pos = event.pos
-        
-        pos_canvas = self.plot.translate_coords(cursor_pos)
-        
-        # we don't need all the info in the tuple returned by the translate_coords()
-        # only first 2 elements
-        pos_canvas = [pos_canvas[0], pos_canvas[1]]
-        self.ui.position_label.setText("Position:  X: %.4f\tY: %.4f" % (pos_canvas[0], pos_canvas[1]))
-        # pos_text = 'Coordinates:   \nX: {:<7.4f}\nY: {:<7.4f}'.format(pos_canvas[0], pos_canvas[1])
-        pos_text = 'Coordinates:   \nX: {:<.4f}\nY: {:<.4f}'.format(pos_canvas[0], pos_canvas[1])
-        self.plot.vispy_canvas.text.text = pos_text
-
-
-if __name__ == '__main__':
-    app = QtWidgets.QApplication(sys.argv)
-
-    m_app = MyApp()
-    sys.exit(app.exec_())

BIN
assets/resources/axis16.png


BIN
assets/resources/clear_line16.png


BIN
assets/resources/contribute256.png


BIN
assets/resources/corners_32.png


BIN
assets/resources/dark_resources/axis16.png


BIN
assets/resources/dark_resources/clear_line16.png


BIN
assets/resources/dark_resources/contribute256.png


BIN
assets/resources/dark_resources/corners_32.png


BIN
assets/resources/dark_resources/edit_file16.png


BIN
assets/resources/dark_resources/edit_file32.png


BIN
assets/resources/dark_resources/etch_32.png


BIN
assets/resources/dark_resources/hud16.png


BIN
assets/resources/dark_resources/hud_32.png


BIN
assets/resources/dark_resources/iso_16.png


BIN
assets/resources/dark_resources/panelize32.png


BIN
assets/resources/dark_resources/settings18.png


BIN
assets/resources/dark_resources/shell20.png


BIN
assets/resources/edit_file16.png


BIN
assets/resources/edit_file32.png


BIN
assets/resources/etch_32.png


BIN
assets/resources/grid32.png


BIN
assets/resources/grid_lines32.png


BIN
assets/resources/hud16.png


BIN
assets/resources/hud_32.png


BIN
assets/resources/iso_16.png


BIN
assets/resources/panelize32.png


BIN
assets/resources/settings18.png


BIN
assets/resources/shell20.png


ファイルの差分が大きいため隠しています
+ 90 - 515
camlib.py


+ 20 - 42
defaults.py

@@ -2,16 +2,16 @@ import os
 import stat
 import sys
 from copy import deepcopy
-from Common import LoudDict
+from FlatCAMCommon import LoudDict
 from camlib import to_dict, CNCjob, Geometry
 import simplejson
 import logging
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
-from AppParsers.ParseExcellon import Excellon
-from AppParsers.ParseGerber import Gerber
+from flatcamParsers.ParseExcellon import Excellon
+from flatcamParsers.ParseGerber import Gerber
 
 fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
@@ -43,7 +43,6 @@ class FlatCAMDefaults:
 
         # General
         "global_graphic_engine": '3D',
-        "global_hud": True,
         "global_app_level": 'b',
         "global_portable": False,
         "global_language": 'English',
@@ -172,6 +171,12 @@ class FlatCAMDefaults:
                                "All Files (*.*)",
 
         # Gerber Options
+        "gerber_isotooldia": 0.1,
+        "gerber_isopasses": 1,
+        "gerber_isooverlap": 10,
+        "gerber_milling_type": "cl",
+        "gerber_combine_passes": False,
+        "gerber_iso_scope": 'all',
         "gerber_noncoppermargin": 0.1,
         "gerber_noncopperrounded": False,
         "gerber_bboxmargin": 0.1,
@@ -182,6 +187,11 @@ class FlatCAMDefaults:
         "gerber_aperture_scale_factor": 1.0,
         "gerber_aperture_buffer_factor": 0.0,
         "gerber_follow": False,
+        "gerber_tool_type": 'circular',
+        "gerber_vtipdia": 0.1,
+        "gerber_vtipangle": 30,
+        "gerber_vcutz": -0.05,
+        "gerber_iso_type": "full",
         "gerber_buffering": "full",
         "gerber_simplification": False,
         "gerber_simp_tolerance": 0.0005,
@@ -212,7 +222,6 @@ class FlatCAMDefaults:
         # Excellon General
         "excellon_plot": True,
         "excellon_solid": True,
-        "excellon_multicolored": False,
         "excellon_format_upper_in": 2,
         "excellon_format_lower_in": 4,
         "excellon_format_upper_mm": 3,
@@ -301,7 +310,6 @@ class FlatCAMDefaults:
 
         # Geometry General
         "geometry_plot": True,
-        "geometry_multicolored": False,
         "geometry_circle_steps": 64,
         "geometry_cnctooldia": "2.4",
         "geometry_plot_line": "#FF0000",
@@ -382,28 +390,6 @@ class FlatCAMDefaults:
         "cncjob_annotation_fontsize": 9,
         "cncjob_annotation_fontcolor": '#990000',
 
-        # Isolation Routing Tool
-        "tools_iso_tooldia": "0.1",
-        "tools_iso_order": 'rev',
-        "tools_iso_tool_type": 'C1',
-        "tools_iso_tool_vtipdia": 0.1,
-        "tools_iso_tool_vtipangle": 30,
-        "tools_iso_tool_cutz": -0.05,
-        "tools_iso_newdia": 0.1,
-
-        "tools_iso_passes": 1,
-        "tools_iso_overlap": 10,
-        "tools_iso_milling_type": "cl",
-        "tools_iso_follow": False,
-        "tools_iso_isotype": "full",
-
-        "tools_iso_rest":           False,
-        "tools_iso_combine_passes": False,
-        "tools_iso_isoexcept":      False,
-        "tools_iso_selection":      _("All"),
-        "tools_iso_area_shape":     "square",
-        "tools_iso_plotting":       'normal',
-
         # NCC Tool
         "tools_ncctools": "1.0, 0.5",
         "tools_nccorder": 'rev',
@@ -418,13 +404,13 @@ class FlatCAMDefaults:
         "tools_ncc_offset_value": 0.0000,
         "tools_nccref": _('Itself'),
         "tools_ncc_area_shape": "square",
+        "tools_ncc_plotting": 'normal',
         "tools_nccmilling_type": 'cl',
         "tools_ncctool_type": 'C1',
         "tools_ncccutz": -0.05,
         "tools_ncctipdia": 0.1,
         "tools_ncctipangle": 30,
         "tools_nccnewdia": 0.1,
-        "tools_ncc_plotting": 'normal',
 
         # Cutout Tool
         "tools_cutouttooldia": 2.4,
@@ -443,7 +429,7 @@ class FlatCAMDefaults:
         "tools_paintoverlap": 20,
         "tools_paintmargin": 0.0,
         "tools_paintmethod": _("Seed"),
-        "tools_selectmethod": _("All"),
+        "tools_selectmethod": _("All Polygons"),
         "tools_paint_area_shape": "square",
         "tools_pathconnect": True,
         "tools_paintcontour": True,
@@ -538,12 +524,6 @@ class FlatCAMDefaults:
         # Distance Tool
         "tools_dist_snap_center": False,
 
-        # Corner Markers Tool
-
-        "tools_corners_thickness": 0.1,
-        "tools_corners_length": 3.0,
-        "tools_corners_margin": 0.0,
-
         # ########################################################################################################
         # ################################ TOOLS 2 ###############################################################
         # ########################################################################################################
@@ -697,15 +677,13 @@ class FlatCAMDefaults:
     }
 
     @classmethod
-    def save_factory_defaults(cls, file_path: str, version: float):
+    def save_factory_defaults(cls, file_path: str):
         """Writes the factory defaults to a file at the given path, overwriting any existing file."""
         # Delete any existing factory defaults file
         if os.path.isfile(file_path):
             os.chmod(file_path, stat.S_IRWXO | stat.S_IWRITE | stat.S_IWGRP)
             os.remove(file_path)
 
-        cls.factory_defaults['version'] = version
-
         try:
             # recreate a new factory defaults file and save the factory defaults data into it
             f_f_def_s = open(file_path, "w")
@@ -786,8 +764,8 @@ class FlatCAMDefaults:
         if defaults is None:
             return
 
-        # Perform migration if necessary but only if the defaults dict is not empty
-        if self.__is_old_defaults(defaults) and defaults:
+        # Perform migration if necessary
+        if self.__is_old_defaults(defaults):
             self.old_defaults_found = True
             defaults = self.__migrate_old_defaults(defaults=defaults)
         else:

+ 48 - 29
AppEditors/FlatCAMExcEditor.py → flatcamEditors/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 AppGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
-from AppEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
-from AppParsers.ParseExcellon import Excellon
+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 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 AppTranslation as fcTranslate
+import FlatCAMTranslation 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 AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            from flatcamGUI.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 Excellon Editor is finished ...")
+        log.debug("Initialization of the FlatCAM 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 AppGUI
+        # Init GUI
         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,8 +2819,10 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.tool_shape.enabled = True
         # self.app.app_cursor.enabled = True
 
-        self.app.ui.corner_snap_btn.setVisible(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.exc_editor_menu.setDisabled(False)
         self.app.ui.exc_editor_menu.menuAction().setVisible(True)
@@ -2830,11 +2832,12 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.app.ui.exc_edit_toolbar.setDisabled(False)
         self.app.ui.exc_edit_toolbar.setVisible(True)
-        # self.app.ui.status_toolbar.setDisabled(False)
+        # self.app.ui.snap_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)
@@ -2866,8 +2869,30 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.clear()
         self.app.ui.exc_edit_toolbar.setDisabled(True)
 
-        self.app.ui.corner_snap_btn.setVisible(False)
-        self.app.ui.snap_magnet.setVisible(False)
+        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)
 
         # 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 \
@@ -3043,7 +3068,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.set_ui()
 
-        # now that we hava data, create the AppGUI interface and add it to the Tool Tab
+        # now that we hava data, create the GUI 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
@@ -3336,17 +3361,15 @@ class FlatCAMExcEditor(QtCore.QObject):
         with self.app.proc_container.new(_("Creating Excellon.")):
 
             try:
-                edited_obj = self.app.app_obj.new_object("excellon", outname, obj_init)
+                edited_obj = self.app.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):
@@ -3440,8 +3463,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:
@@ -3778,22 +3801,18 @@ 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 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)
+        # 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))
 
         # ## Utility geometry (animated)
         self.update_utility_geometry(data=(x, y))
@@ -4026,7 +4045,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
     def select_tool(self, toolname):
         """
-        Selects a drawing tool. Impacts the object and AppGUI.
+        Selects a drawing tool. Impacts the object and GUI.
 
         :param toolname: Name of the tool.
         :return: None

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

@@ -15,10 +15,11 @@ from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 
 from camlib import distance, arc, three_point_circle, Geometry, FlatCAMRTreeStorage
-from AppTool import AppTool
-from AppGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
-    FCDoubleSpinner, FCButton, FCInputDialog, FCTree
-from AppParsers.ParseFont import *
+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 shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
 from shapely.ops import cascaded_union, unary_union, linemerge
@@ -33,7 +34,7 @@ from rtree import index as rtindex
 from copy import deepcopy
 # from vispy.io import read_png
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -41,7 +42,7 @@ if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
 
-class BufferSelectionTool(AppTool):
+class BufferSelectionTool(FlatCAMTool):
     """
     Simple input for buffer distance.
     """
@@ -49,7 +50,7 @@ class BufferSelectionTool(AppTool):
     toolName = "Buffer Selection"
 
     def __init__(self, app, draw_app):
-        AppTool.__init__(self, app)
+        FlatCAMTool.__init__(self, app)
 
         self.draw_app = draw_app
         self.decimals = app.decimals
@@ -117,12 +118,12 @@ class BufferSelectionTool(AppTool):
         self.buffer_int_button.clicked.connect(self.on_buffer_int)
         self.buffer_ext_button.clicked.connect(self.on_buffer_ext)
 
-        # Init AppGUI
+        # Init GUI
         self.buffer_distance_entry.set_value(0.01)
 
     def run(self):
         self.app.defaults.report_usage("Geo Editor ToolBuffer()")
-        AppTool.run(self)
+        FlatCAMTool.run(self)
 
         # if the splitter us hidden, display it
         if self.app.ui.splitter.sizes()[0] == 0:
@@ -186,7 +187,7 @@ class BufferSelectionTool(AppTool):
         self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
 
 
-class TextInputTool(AppTool):
+class TextInputTool(FlatCAMTool):
     """
     Simple input for buffer distance.
     """
@@ -194,7 +195,7 @@ class TextInputTool(AppTool):
     toolName = "Text Input Tool"
 
     def __init__(self, app):
-        AppTool.__init__(self, app)
+        FlatCAMTool.__init__(self, app)
 
         self.app = app
         self.text_path = []
@@ -339,7 +340,7 @@ class TextInputTool(AppTool):
 
     def run(self):
         self.app.defaults.report_usage("Geo Editor TextInputTool()")
-        AppTool.run(self)
+        FlatCAMTool.run(self)
 
         # if the splitter us hidden, display it
         if self.app.ui.splitter.sizes()[0] == 0:
@@ -404,7 +405,7 @@ class TextInputTool(AppTool):
         self.app.ui.notebook.setTabText(2, _("Tool"))
 
 
-class PaintOptionsTool(AppTool):
+class PaintOptionsTool(FlatCAMTool):
     """
     Inputs to specify how to paint the selected polygons.
     """
@@ -412,7 +413,7 @@ class PaintOptionsTool(AppTool):
     toolName = "Paint Tool"
 
     def __init__(self, app, fcdraw):
-        AppTool.__init__(self, app)
+        FlatCAMTool.__init__(self, app)
 
         self.app = app
         self.fcdraw = fcdraw
@@ -537,7 +538,7 @@ class PaintOptionsTool(AppTool):
 
     def run(self):
         self.app.defaults.report_usage("Geo Editor ToolPaint()")
-        AppTool.run(self)
+        FlatCAMTool.run(self)
 
         # if the splitter us hidden, display it
         if self.app.ui.splitter.sizes()[0] == 0:
@@ -546,7 +547,7 @@ class PaintOptionsTool(AppTool):
         self.app.ui.notebook.setTabText(2, _("Paint Tool"))
 
     def set_tool_ui(self):
-        # Init AppGUI
+        # Init GUI
         if self.app.defaults["tools_painttooldia"]:
             self.painttooldia_entry.set_value(self.app.defaults["tools_painttooldia"])
         else:
@@ -598,7 +599,7 @@ class PaintOptionsTool(AppTool):
         self.app.ui.splitter.setSizes([0, 1])
 
 
-class TransformEditorTool(AppTool):
+class TransformEditorTool(FlatCAMTool):
     """
     Inputs to specify how to paint the selected polygons.
     """
@@ -611,7 +612,7 @@ class TransformEditorTool(AppTool):
     offsetName = _("Offset")
 
     def __init__(self, app, draw_app):
-        AppTool.__init__(self, app)
+        FlatCAMTool.__init__(self, app)
 
         self.app = app
         self.draw_app = draw_app
@@ -980,7 +981,7 @@ class TransformEditorTool(AppTool):
 
     def run(self):
         self.app.defaults.report_usage("Geo Editor Transform Tool()")
-        AppTool.run(self)
+        FlatCAMTool.run(self)
         self.set_tool_ui()
 
         # if the splitter us hidden, display it
@@ -990,7 +991,7 @@ class TransformEditorTool(AppTool):
         self.app.ui.notebook.setTabText(2, _("Transform Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
 
     def set_tool_ui(self):
         # Initialize form
@@ -3382,7 +3383,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 AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            from flatcamGUI.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')
 
@@ -3466,32 +3467,22 @@ class FlatCAMGeoEditor(QtCore.QObject):
             :return:
             """
             try:
-                text_value = entry.text()
-                if ',' in text_value:
-                    text_value = text_value.replace(',', '.')
-                self.options[opt] = float(text_value)
+                self.options[opt] = float(entry.text())
             except Exception as e:
-                entry.set_value(self.app.defaults[opt])
                 log.debug("FlatCAMGeoEditor.__init__().entry2option() --> %s" % str(e))
                 return
 
-        def grid_changed(goption, gentry):
+        def gridx_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:
-                text_value = gentry.text()
-                if ',' in text_value:
-                    text_value = text_value.replace(',', '.')
-                val = float(text_value)
+                val = float(gentry.get_value())
             except ValueError:
                 return
 
@@ -3500,7 +3491,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: grid_changed("global_gridx", self.app.ui.grid_gap_x_entry))
+            lambda: gridx_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(
@@ -3551,7 +3542,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 Geometry Editor is finished ...")
+        log.debug("Initialization of the FlatCAM Geometry Editor is finished ...")
 
     def pool_recreated(self, pool):
         self.shapes.pool = pool
@@ -3568,14 +3559,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # Remove anything else in the GUI Selected Tab
         self.app.ui.selected_scroll_area.takeWidget()
-        # Put ourselves in the AppGUI Selected Tab
+        # Put ourselves in the GUI 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 AppGUI in the Selected Tab for this editor
+        Build the GUI in the Selected Tab for this editor
 
         :return:
         """
@@ -3653,8 +3644,10 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.tool_shape.enabled = True
         self.app.app_cursor.enabled = True
 
-        self.app.ui.corner_snap_btn.setVisible(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.geo_editor_menu.setDisabled(False)
         self.app.ui.geo_editor_menu.menuAction().setVisible(True)
@@ -3665,7 +3658,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.app.ui.geo_edit_toolbar.setDisabled(False)
         self.app.ui.geo_edit_toolbar.setVisible(True)
 
-        self.app.ui.status_toolbar.setDisabled(False)
+        self.app.ui.snap_toolbar.setDisabled(False)
 
         self.app.ui.popmenu_disable.setVisible(False)
         self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
@@ -3682,7 +3675,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         self.item_selected.connect(self.on_geo_elem_selected)
 
-        # ## AppGUI Events
+        # ## GUI 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)
@@ -3710,8 +3703,27 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.app.ui.geo_edit_toolbar.setDisabled(True)
 
         settings = QSettings("Open Source", "FlatCAM")
-        self.app.ui.corner_snap_btn.setVisible(False)
-        self.app.ui.snap_magnet.setVisible(False)
+        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)
 
         # 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 \
@@ -3745,7 +3757,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
             pass
 
         try:
-            # ## AppGUI Events
+            # ## GUI 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)
@@ -4088,6 +4100,7 @@ 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)
@@ -4135,11 +4148,9 @@ 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):
         """
@@ -4162,8 +4173,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
@@ -4250,23 +4261,18 @@ 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 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)
+        # 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))
 
         if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
             pass
@@ -4659,7 +4665,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
     def select_tool(self, toolname):
         """
-        Selects a drawing tool. Impacts the object and AppGUI.
+        Selects a drawing tool. Impacts the object and GUI.
 
         :param toolname: Name of the tool.
         :return: None
@@ -4744,8 +4750,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'] = []
@@ -4770,8 +4776,6 @@ 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))

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

@@ -14,13 +14,15 @@ 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 AppGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
+from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
     EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
-from AppTool import AppTool
+from FlatCAMTool import FlatCAMTool
 
 import numpy as np
 from numpy.linalg import norm as numpy_norm
@@ -30,7 +32,7 @@ import math
 # import pngcanvas
 import traceback
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -1084,6 +1086,15 @@ 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':
@@ -1149,36 +1160,16 @@ 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(FCShapeTool):
+class FCTrack(FCRegion):
     """
     Resulting type: Polygon
     """
     def __init__(self, draw_app):
-        DrawTool.__init__(self, draw_app)
+        FCRegion.__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:
@@ -1192,23 +1183,52 @@ class FCTrack(FCShapeTool):
 
         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
-
-        if not self.points:
-            self.points.append(point)
-        elif point != self.points[-1]:
+        try:
+            if point != self.points[-1]:
+                self.points.append(point)
+        except IndexError:
             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, int(self.steps_per_circle))
+            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, int(self.steps_per_circle))
+            new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val,
+                                                                      resolution=int(self.steps_per_circle / 4))
             new_geo_el['follow'] = LineString(self.temp_points)
 
         self.draw_app.add_gerber_shape(DrawToolShape(new_geo_el),
@@ -1221,25 +1241,23 @@ class FCTrack(FCShapeTool):
 
         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 not self.points:
-            new_geo_el['solid'] = Point(data).buffer(self.buf_val, int(self.steps_per_circle))
+        if len(self.points) == 0:
+            new_geo_el['solid'] = Point(data).buffer(self.buf_val,
+                                                     resolution=int(self.steps_per_circle / 4))
+
             return DrawToolUtilityShape(new_geo_el)
-        else:
+        elif len(self.points) > 0:
+
+            self.temp_points = [self.points[-1]]
             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))
 
@@ -1287,30 +1305,14 @@ class FCTrack(FCShapeTool):
 
             self.temp_points.append(data)
             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['solid'] = Point(self.temp_points).buffer(self.buf_val,
+                                                                     resolution=int(self.steps_per_circle / 4))
                 return DrawToolUtilityShape(new_geo_el)
 
-            new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
+            new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val,
+                                                                      resolution=int(self.steps_per_circle / 4))
             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:
@@ -1403,15 +1405,6 @@ class FCTrack(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 FCDisc(FCShapeTool):
     """
@@ -2962,7 +2955,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # this var will store the state of the toolbar before starting the editor
         self.toolbar_old_state = False
 
-        # Init AppGUI
+        # Init GUI
         self.apdim_lbl.hide()
         self.apdim_entry.hide()
         self.gerber_obj = None
@@ -2974,7 +2967,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 AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            from flatcamGUI.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(
@@ -3117,7 +3110,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.complete = True
 
         self.set_ui()
-        log.debug("Initialization of the Gerber Editor is finished ...")
+        log.debug("Initialization of the FlatCAM Gerber Editor is finished ...")
 
     def pool_recreated(self, pool):
         self.shapes.pool = pool
@@ -3146,7 +3139,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             tt_aperture = self.sorted_apcode[i]
             self.tid2apcode[i + 1] = tt_aperture
 
-        # Init AppGUI
+        # Init GUI
 
         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"])
@@ -3435,7 +3428,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 = []
@@ -3692,8 +3685,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.shapes.enabled = True
         self.tool_shape.enabled = True
 
-        self.app.ui.corner_snap_btn.setVisible(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.grb_editor_menu.setDisabled(False)
         self.app.ui.grb_editor_menu.menuAction().setVisible(True)
@@ -3703,11 +3698,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.app.ui.grb_edit_toolbar.setDisabled(False)
         self.app.ui.grb_edit_toolbar.setVisible(True)
-        # self.app.ui.status_toolbar.setDisabled(False)
+        # self.app.ui.snap_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)
@@ -3740,8 +3736,29 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.grb_edit_toolbar.setDisabled(True)
 
         settings = QSettings("Open Source", "FlatCAM")
-        self.app.ui.corner_snap_btn.setVisible(False)
-        self.app.ui.snap_magnet.setVisible(False)
+        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)
 
         # 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 \
@@ -4193,7 +4210,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 AppGUI"))
+        self.app.inform.emit('[success] %s.' % _("Adding geometry finished. Preparing the GUI"))
         self.set_ui()
         self.build_ui(first_run=True)
         self.plot_all()
@@ -4228,7 +4245,6 @@ 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):
@@ -4250,10 +4266,9 @@ 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
-        :type aperture_storage:     dict
+        :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
         :return: None
         """
 
@@ -4345,27 +4360,26 @@ 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.app_obj.new_object("gerber", outname, obj_init)
+                self.app.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):
         """
@@ -4523,8 +4537,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:
@@ -4536,7 +4550,8 @@ 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
@@ -4547,7 +4562,6 @@ 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()
@@ -4555,7 +4569,6 @@ 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:
@@ -4616,14 +4629,12 @@ 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):
-                            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()
+                            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
@@ -4763,23 +4774,18 @@ 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 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)
+        # 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))
 
         self.update_utility_geometry(data=(x, y))
 
@@ -5026,7 +5032,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
     def select_tool(self, toolname):
         """
-        Selects a drawing tool. Impacts the object and AppGUI.
+        Selects a drawing tool. Impacts the object and GUI.
 
         :param toolname: Name of the tool.
         :return: None
@@ -5292,7 +5298,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
 
 
-class TransformEditorTool(AppTool):
+class TransformEditorTool(FlatCAMTool):
     """
     Inputs to specify how to paint the selected polygons.
     """
@@ -5305,7 +5311,7 @@ class TransformEditorTool(AppTool):
     offsetName = _("Offset")
 
     def __init__(self, app, draw_app):
-        AppTool.__init__(self, app)
+        FlatCAMTool.__init__(self, app)
 
         self.app = app
         self.draw_app = draw_app
@@ -5691,13 +5697,13 @@ class TransformEditorTool(AppTool):
             except AttributeError:
                 pass
 
-        AppTool.run(self)
+        FlatCAMTool.run(self)
         self.set_tool_ui()
 
         self.app.ui.notebook.setTabText(2, _("Transform Tool"))
 
     def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
+        FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
 
     def set_tool_ui(self):
         # Initialize form

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

@@ -5,7 +5,7 @@
 # MIT Licence                                              #
 # ##########################################################
 
-from AppGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber
+from flatcamGUI.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 AppTranslation as fcTranslate
+import FlatCAMTranslation 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),
-                ext_filter=_filter_
+                filter=_filter_
             )[0])
         except TypeError:
-            filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)[0])
+            filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), filter=_filter_)[0])
 
         if filename == "":
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))

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


ファイルの差分が大きいため隠しています
+ 105 - 2449
flatcamGUI/FlatCAMGUI.py


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

@@ -23,7 +23,7 @@ import html
 import sys
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 log = logging.getLogger('base')
@@ -570,13 +570,9 @@ class FCEntry3(FCEntry):
 
 
 class EvalEntry(QtWidgets.QLineEdit):
-    def __init__(self, border_color=None, parent=None):
+    def __init__(self, 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):
@@ -603,6 +599,7 @@ class EvalEntry(QtWidgets.QLineEdit):
 
     def get_value(self):
         raw = str(self.text()).strip(' ')
+        evaled = 0.0
         try:
             evaled = eval(raw)
         except Exception as e:
@@ -642,7 +639,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:
@@ -659,132 +656,6 @@ 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()
@@ -813,8 +684,6 @@ 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:
@@ -947,8 +816,6 @@ 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()
@@ -2231,24 +2098,6 @@ 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
@@ -2347,14 +2196,11 @@ class OptionalHideInputSection:
         """
         Associates the a checkbox with a set of inputs.
 
-        :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
+        :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
         :return:
         """
         assert isinstance(cb, FCCheckBox), \
@@ -2625,8 +2471,7 @@ class SpinBoxDelegate(QtWidgets.QItemDelegate):
     def updateEditorGeometry(self, editor, option, index):
         editor.setGeometry(option.rect)
 
-    @staticmethod
-    def setDecimals(spinbox, digits):
+    def setDecimals(self, spinbox, digits):
         spinbox.setDecimals(digits)
 
 
@@ -2674,7 +2519,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__(parent=parent)
+        super(DialogBoxRadio, self).__init__()
         if initial_text is None:
             self.location = str((0, 0))
         else:
@@ -2917,12 +2762,9 @@ class MyCompleter(QCompleter):
     insertText = QtCore.pyqtSignal(str)
 
     def __init__(self, parent=None):
-        QCompleter.__init__(self, parent=parent)
+        QCompleter.__init__(self)
         self.setCompletionMode(QCompleter.PopupCompletion)
         self.highlighted.connect(self.setHighlighted)
-
-        self.lastSelected = ''
-
         # self.popup().installEventFilter(self)
 
     # def eventFilter(self, obj, event):
@@ -3078,9 +2920,9 @@ class FCFileSaveDialog(QtWidgets.QFileDialog):
         super(FCFileSaveDialog, self).__init__(*args)
 
     @staticmethod
-    def get_saved_filename(parent=None, caption='', directory='', ext_filter='', initialFilter=''):
+    def get_saved_filename(parent=None, caption='', directory='', filter='', initialFilter=''):
         filename, _filter = QtWidgets.QFileDialog.getSaveFileName(parent=parent, caption=caption,
-                                                                  directory=directory, filter=ext_filter,
+                                                                  directory=directory, filter=filter,
                                                                   initialFilter=initialFilter)
 
         filename = str(filename)
@@ -3096,225 +2938,6 @@ 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:

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

@@ -11,11 +11,11 @@
 # Date: 3/10/2019                                          #
 # ##########################################################
 
-from AppGUI.GUIElements import *
+from flatcamGUI.GUIElements import *
 import sys
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation 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=_('App Object'),
+    def __init__(self, app, icon_file='assets/resources/flatcam_icon32.png', title=_('FlatCAM 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 = NumericalEvalEntry(border_color='#0069A9')
+            self.scale_entry = FCEntry()
             self.scale_entry.set_value(1.0)
             self.scale_entry.setToolTip(
                 _("Factor by which to multiply\n"
@@ -132,7 +132,7 @@ class ObjectUI(QtWidgets.QWidget):
             self.common_grid.addWidget(self.scale_button, 3, 1)
 
             # ### Offset ####
-            self.offsetvector_entry = NumericalEvalTupleEntry(border_color='#0069A9')
+            self.offsetvector_entry = EvalEntry2()
             self.offsetvector_entry.setText("(0.0, 0.0)")
             self.offsetvector_entry.setToolTip(
                 _("Amount by which to move the object\n"
@@ -149,30 +149,21 @@ 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[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
-                                                                                  self.decimals,
-                                                                                  minval,
-                                                                                  self.decimals,
-                                                                                  maxval), False)
+            self.app.inform.emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' %
+                                 (_("Edited value is out of range"), self.decimals, minval, self.decimals, maxval))
         else:
-            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
 
     def confirmation_message_int(self, accepted, minval, maxval):
         if accepted is False:
-            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
-                                 (_("Edited value is out of range"), minval, maxval), False)
+            self.app.inform.emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                 (_("Edited value is out of range"), minval, maxval))
         else:
-            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+            self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
 
 
 class GerberObjectUI(ObjectUI):
@@ -214,37 +205,27 @@ 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()
-        grid0.addLayout(self.name_hlay, 1, 0, 1, 3)
-
+        self.custom_box.addLayout(self.name_hlay)
         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('%s:' % _('Apertures'))
+        self.apertures_table_label = QtWidgets.QLabel('<b>%s:</b>' % _('Apertures'))
         self.apertures_table_label.setToolTip(
             _("Apertures Table for the Gerber Object.")
         )
@@ -301,7 +282,254 @@ class GerberObjectUI(ObjectUI):
         # start with apertures table hidden
         self.apertures_table.setVisible(False)
 
-        # Buffer Geometry
+        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)
+
         self.create_buffer_button = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
         self.create_buffer_button.setToolTip(
             _("This button is shown only when the Gerber file\n"
@@ -309,12 +537,19 @@ class GerberObjectUI(ObjectUI):
               "Clicking this will create the buffered geometry\n"
               "required for isolation.")
         )
-        self.custom_box.addWidget(self.create_buffer_button)
+        grid1.addWidget(self.create_buffer_button, 13, 0, 1, 3)
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.custom_box.addWidget(separator_line)
+        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)
 
         # ###########################################
         # ########## NEW GRID #######################
@@ -328,21 +563,14 @@ class GerberObjectUI(ObjectUI):
         self.tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("TOOLS"))
         grid2.addWidget(self.tool_lbl, 0, 0, 1, 2)
 
-        # Isolation Tool - will create isolation paths around the copper features
-        self.iso_button = QtWidgets.QPushButton(_('Isolation Routing'))
-        self.iso_button.setToolTip(
+        # ## Clear non-copper regions
+        self.clearcopper_label = QtWidgets.QLabel("%s" % _("Clear N-copper"))
+        self.clearcopper_label.setToolTip(
             _("Create a Geometry object with\n"
-              "toolpaths to cut around polygons.")
+              "toolpaths to cut all non-copper regions.")
         )
-        self.iso_button.setStyleSheet("""
-                                      QPushButton
-                                      {
-                                          font-weight: bold;
-                                      }
-                                      """)
-        grid2.addWidget(self.iso_button, 1, 0, 1, 2)
+        self.clearcopper_label.setMinimumWidth(90)
 
-        # ## Clear non-copper regions
         self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool'))
         self.generate_ncc_button.setToolTip(
             _("Create the Geometry Object\n"
@@ -354,9 +582,17 @@ class GerberObjectUI(ObjectUI):
                             font-weight: bold;
                         }
                         """)
-        grid2.addWidget(self.generate_ncc_button, 2, 0, 1, 2)
+        grid2.addWidget(self.clearcopper_label, 1, 0)
+        grid2.addWidget(self.generate_ncc_button, 1, 1)
 
         # ## 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"
@@ -368,12 +604,13 @@ class GerberObjectUI(ObjectUI):
                             font-weight: bold;
                         }
                         """)
-        grid2.addWidget(self.generate_cutout_button, 3, 0, 1, 2)
+        grid2.addWidget(self.board_cutout_label, 2, 0)
+        grid2.addWidget(self.generate_cutout_button, 2, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid2.addWidget(separator_line, 4, 0, 1, 2)
+        grid2.addWidget(separator_line, 3, 0, 1, 2)
 
         # ## Non-copper regions
         self.noncopper_label = QtWidgets.QLabel("<b>%s</b>" % _("Non-copper regions"))
@@ -385,7 +622,7 @@ class GerberObjectUI(ObjectUI):
               "copper from a specified region.")
         )
 
-        grid2.addWidget(self.noncopper_label, 5, 0, 1, 2)
+        grid2.addWidget(self.noncopper_label, 4, 0, 1, 2)
 
         # Margin
         bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
@@ -401,8 +638,8 @@ class GerberObjectUI(ObjectUI):
         self.noncopper_margin_entry.set_precision(self.decimals)
         self.noncopper_margin_entry.setSingleStep(0.1)
 
-        grid2.addWidget(bmlabel, 6, 0)
-        grid2.addWidget(self.noncopper_margin_entry, 6, 1)
+        grid2.addWidget(bmlabel, 5, 0)
+        grid2.addWidget(self.noncopper_margin_entry, 5, 1)
 
         # Rounded corners
         self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
@@ -412,13 +649,13 @@ class GerberObjectUI(ObjectUI):
         self.noncopper_rounded_cb.setMinimumWidth(90)
 
         self.generate_noncopper_button = QtWidgets.QPushButton(_('Generate Geo'))
-        grid2.addWidget(self.noncopper_rounded_cb, 7, 0)
-        grid2.addWidget(self.generate_noncopper_button, 7, 1)
+        grid2.addWidget(self.noncopper_rounded_cb, 6, 0)
+        grid2.addWidget(self.generate_noncopper_button, 6, 1)
 
         separator_line1 = QtWidgets.QFrame()
         separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid2.addWidget(separator_line1, 8, 0, 1, 2)
+        grid2.addWidget(separator_line1, 7, 0, 1, 2)
 
         # ## Bounding box
         self.boundingbox_label = QtWidgets.QLabel('<b>%s</b>' % _('Bounding Box'))
@@ -427,7 +664,7 @@ class GerberObjectUI(ObjectUI):
               "Square shape.")
         )
 
-        grid2.addWidget(self.boundingbox_label, 9, 0, 1, 2)
+        grid2.addWidget(self.boundingbox_label, 8, 0, 1, 2)
 
         bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
         bbmargin.setToolTip(
@@ -440,8 +677,8 @@ class GerberObjectUI(ObjectUI):
         self.bbmargin_entry.set_precision(self.decimals)
         self.bbmargin_entry.setSingleStep(0.1)
 
-        grid2.addWidget(bbmargin, 10, 0)
-        grid2.addWidget(self.bbmargin_entry, 10, 1)
+        grid2.addWidget(bbmargin, 9, 0)
+        grid2.addWidget(self.bbmargin_entry, 9, 1)
 
         self.bbrounded_cb = FCCheckBox(label=_("Rounded Geo"))
         self.bbrounded_cb.setToolTip(
@@ -456,8 +693,8 @@ class GerberObjectUI(ObjectUI):
         self.generate_bb_button.setToolTip(
             _("Generate the Geometry object.")
         )
-        grid2.addWidget(self.bbrounded_cb, 11, 0)
-        grid2.addWidget(self.generate_bb_button, 11, 1)
+        grid2.addWidget(self.bbrounded_cb, 10, 0)
+        grid2.addWidget(self.generate_bb_button, 10, 1)
 
 
 class ExcellonObjectUI(ObjectUI):
@@ -486,38 +723,22 @@ class ExcellonObjectUI(ObjectUI):
                           parent=parent,
                           app=self.app)
 
-        # 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)
+        # ### Plot options ####
+        hlay_plot = QtWidgets.QHBoxLayout()
+        self.custom_box.addLayout(hlay_plot)
 
         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.")
         )
-        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)
+        hlay_plot.addWidget(self.plot_options_label)
+        hlay_plot.addStretch()
+        hlay_plot.addWidget(self.solid_cb)
 
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
-        grid_h.addLayout(self.name_hlay, 1, 0, 1, 3)
-
+        self.custom_box.addLayout(self.name_hlay)
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
@@ -1304,28 +1525,12 @@ 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.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)
+        self.custom_box.addWidget(self.plot_options_label)
 
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
-        grid_header.addLayout(self.name_hlay, 1, 0, 1, 3)
+        self.custom_box.addLayout(self.name_hlay)
 
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
@@ -1489,24 +1694,19 @@ 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 diameter specified above.")
+              "with the specified diameter.")
         )
+        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.")
         )
-
-        bhlay.addWidget(self.addtool_btn)
-        bhlay.addWidget(self.addtool_from_db_btn)
-
-        grid1.addLayout(bhlay, 5, 0, 1, 2)
+        grid1.addWidget(self.addtool_from_db_btn, 7, 0, 1, 2)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -1554,10 +1754,10 @@ class GeometryObjectUI(ObjectUI):
         # ################# GRID LAYOUT 3   ###############################
         # #################################################################
 
-        self.grid3 = QtWidgets.QGridLayout()
-        self.grid3.setColumnStretch(0, 0)
-        self.grid3.setColumnStretch(1, 1)
-        self.geo_param_box.addLayout(self.grid3)
+        grid3 = QtWidgets.QGridLayout()
+        grid3.setColumnStretch(0, 0)
+        grid3.setColumnStretch(1, 1)
+        self.geo_param_box.addLayout(grid3)
 
         # ### Tools Data ## ##
         self.tool_data_label = QtWidgets.QLabel(
@@ -1568,7 +1768,7 @@ class GeometryObjectUI(ObjectUI):
                 "Each tool store it's own set of such data."
             )
         )
-        self.grid3.addWidget(self.tool_data_label, 0, 0, 1, 2)
+        grid3.addWidget(self.tool_data_label, 0, 0, 1, 2)
 
         # Tip Dia
         self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
@@ -1582,8 +1782,8 @@ class GeometryObjectUI(ObjectUI):
         self.tipdia_entry.set_range(0.00001, 9999.9999)
         self.tipdia_entry.setSingleStep(0.1)
 
-        self.grid3.addWidget(self.tipdialabel, 1, 0)
-        self.grid3.addWidget(self.tipdia_entry, 1, 1)
+        grid3.addWidget(self.tipdialabel, 1, 0)
+        grid3.addWidget(self.tipdia_entry, 1, 1)
 
         # Tip Angle
         self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
@@ -1598,8 +1798,8 @@ class GeometryObjectUI(ObjectUI):
         self.tipangle_entry.set_range(1.0, 180.0)
         self.tipangle_entry.setSingleStep(1)
 
-        self.grid3.addWidget(self.tipanglelabel, 2, 0)
-        self.grid3.addWidget(self.tipangle_entry, 2, 1)
+        grid3.addWidget(self.tipanglelabel, 2, 0)
+        grid3.addWidget(self.tipangle_entry, 2, 1)
 
         # Cut Z
         self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
@@ -1619,8 +1819,8 @@ class GeometryObjectUI(ObjectUI):
 
         self.cutz_entry.setSingleStep(0.1)
 
-        self.grid3.addWidget(self.cutzlabel, 3, 0)
-        self.grid3.addWidget(self.cutz_entry, 3, 1)
+        grid3.addWidget(self.cutzlabel, 3, 0)
+        grid3.addWidget(self.cutz_entry, 3, 1)
 
         # Multi-pass
         self.mpass_cb = FCCheckBox('%s:' % _("Multi-Depth"))
@@ -1645,8 +1845,8 @@ class GeometryObjectUI(ObjectUI):
         )
         self.ois_mpass_geo = OptionalInputSection(self.mpass_cb, [self.maxdepth_entry])
 
-        self.grid3.addWidget(self.mpass_cb, 4, 0)
-        self.grid3.addWidget(self.maxdepth_entry, 4, 1)
+        grid3.addWidget(self.mpass_cb, 4, 0)
+        grid3.addWidget(self.maxdepth_entry, 4, 1)
 
         # Travel Z
         self.travelzlabel = QtWidgets.QLabel('%s:' % _('Travel Z'))
@@ -1664,8 +1864,8 @@ class GeometryObjectUI(ObjectUI):
 
         self.travelz_entry.setSingleStep(0.1)
 
-        self.grid3.addWidget(self.travelzlabel, 5, 0)
-        self.grid3.addWidget(self.travelz_entry, 5, 1)
+        grid3.addWidget(self.travelzlabel, 5, 0)
+        grid3.addWidget(self.travelz_entry, 5, 1)
 
         # Feedrate X-Y
         self.frlabel = QtWidgets.QLabel('%s:' % _('Feedrate X-Y'))
@@ -1678,8 +1878,8 @@ class GeometryObjectUI(ObjectUI):
         self.cncfeedrate_entry.set_range(0, 99999.9999)
         self.cncfeedrate_entry.setSingleStep(0.1)
 
-        self.grid3.addWidget(self.frlabel, 10, 0)
-        self.grid3.addWidget(self.cncfeedrate_entry, 10, 1)
+        grid3.addWidget(self.frlabel, 10, 0)
+        grid3.addWidget(self.cncfeedrate_entry, 10, 1)
 
         # Feedrate Z (Plunge)
         self.frzlabel = QtWidgets.QLabel('%s:' % _('Feedrate Z'))
@@ -1693,8 +1893,8 @@ class GeometryObjectUI(ObjectUI):
         self.feedrate_z_entry.set_range(0, 99999.9999)
         self.feedrate_z_entry.setSingleStep(0.1)
 
-        self.grid3.addWidget(self.frzlabel, 11, 0)
-        self.grid3.addWidget(self.feedrate_z_entry, 11, 1)
+        grid3.addWidget(self.frzlabel, 11, 0)
+        grid3.addWidget(self.feedrate_z_entry, 11, 1)
 
         # Feedrate rapids
         self.fr_rapidlabel = QtWidgets.QLabel('%s:' % _('Feedrate Rapids'))
@@ -1710,8 +1910,8 @@ class GeometryObjectUI(ObjectUI):
         self.feedrate_rapid_entry.set_range(0, 99999.9999)
         self.feedrate_rapid_entry.setSingleStep(0.1)
 
-        self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
-        self.grid3.addWidget(self.feedrate_rapid_entry, 12, 1)
+        grid3.addWidget(self.fr_rapidlabel, 12, 0)
+        grid3.addWidget(self.feedrate_rapid_entry, 12, 1)
         # default values is to hide
         self.fr_rapidlabel.hide()
         self.feedrate_rapid_entry.hide()
@@ -1736,8 +1936,8 @@ class GeometryObjectUI(ObjectUI):
               "meet with last cut, we generate an\n"
               "extended cut over the first cut section.")
         )
-        self.grid3.addWidget(self.extracut_cb, 13, 0)
-        self.grid3.addWidget(self.e_cut_entry, 13, 1)
+        grid3.addWidget(self.extracut_cb, 13, 0)
+        grid3.addWidget(self.e_cut_entry, 13, 1)
 
         # Spindlespeed
         self.spindle_label = QtWidgets.QLabel('%s:' % _('Spindle speed'))
@@ -1752,8 +1952,8 @@ class GeometryObjectUI(ObjectUI):
         self.cncspindlespeed_entry.set_range(0, 1000000)
         self.cncspindlespeed_entry.set_step(100)
 
-        self.grid3.addWidget(self.spindle_label, 14, 0)
-        self.grid3.addWidget(self.cncspindlespeed_entry, 14, 1)
+        grid3.addWidget(self.spindle_label, 14, 0)
+        grid3.addWidget(self.cncspindlespeed_entry, 14, 1)
 
         # Dwell
         self.dwell_cb = FCCheckBox('%s:' % _('Dwell'))
@@ -1773,8 +1973,8 @@ class GeometryObjectUI(ObjectUI):
         )
         self.ois_dwell_geo = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
 
-        self.grid3.addWidget(self.dwell_cb, 15, 0)
-        self.grid3.addWidget(self.dwelltime_entry, 15, 1)
+        grid3.addWidget(self.dwell_cb, 15, 0)
+        grid3.addWidget(self.dwelltime_entry, 15, 1)
 
         # Probe depth
         self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
@@ -1787,8 +1987,8 @@ class GeometryObjectUI(ObjectUI):
         self.pdepth_entry.set_range(-9999.9999, 9999.9999)
         self.pdepth_entry.setSingleStep(0.1)
 
-        self.grid3.addWidget(self.pdepth_label, 17, 0)
-        self.grid3.addWidget(self.pdepth_entry, 17, 1)
+        grid3.addWidget(self.pdepth_label, 17, 0)
+        grid3.addWidget(self.pdepth_entry, 17, 1)
 
         self.pdepth_label.hide()
         self.pdepth_entry.setVisible(False)
@@ -1803,8 +2003,8 @@ class GeometryObjectUI(ObjectUI):
         self.feedrate_probe_entry.set_range(0.0, 9999.9999)
         self.feedrate_probe_entry.setSingleStep(0.1)
 
-        self.grid3.addWidget(self.feedrate_probe_label, 18, 0)
-        self.grid3.addWidget(self.feedrate_probe_entry, 18, 1)
+        grid3.addWidget(self.feedrate_probe_label, 18, 0)
+        grid3.addWidget(self.feedrate_probe_entry, 18, 1)
 
         self.feedrate_probe_label.hide()
         self.feedrate_probe_entry.setVisible(False)

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

@@ -8,21 +8,13 @@
 from PyQt5 import QtCore
 
 import logging
-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
+from flatcamGUI.VisPyCanvas import VisPyCanvas, Color
+from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
+from vispy.scene.visuals import InfiniteLine, Line
 
 import numpy as np
 from vispy.geometry import Rect
 
-fcTranslate.apply_language('strings')
-if '_' not in builtins.__dict__:
-    _ = gettext.gettext
-
 log = logging.getLogger('base')
 
 
@@ -62,12 +54,8 @@ 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
@@ -141,6 +129,11 @@ 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
@@ -153,61 +146,13 @@ 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
@@ -218,76 +163,6 @@ 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.
@@ -308,30 +183,17 @@ 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
-        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;
-                        }
-                        """)
+        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
 
     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):

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

@@ -19,10 +19,8 @@ from shapely.geometry import Polygon, LineString, LinearRing
 from copy import deepcopy
 import logging
 
-import numpy as np
-
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 # Prevent conflict with Qt5 and above.
@@ -31,13 +29,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')
 
 
@@ -47,9 +45,9 @@ class CanvasCache(QtCore.QObject):
     Case story #1:
 
     1) No objects in the project.
-    2) Object is created (app_obj.new_object() emits object_created(obj)).
+    2) Object is created (new_object() emits object_created(obj)).
        on_object_created() adds (i) object to collection and emits
-       (ii) app_obj.new_object_available() then calls (iii) object.plot()
+       (ii) 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).
@@ -115,7 +113,7 @@ class CanvasCache(QtCore.QObject):
 
         # Continue to update the cache.
 
-    # def on_app_obj.new_object_available(self):
+    # def on_new_object_available(self):
     #
     #     log.debug("A new object is available. Should plot it!")
 
@@ -149,13 +147,9 @@ 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
@@ -301,163 +295,14 @@ 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.
@@ -484,23 +329,12 @@ 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):
         """
@@ -589,7 +423,7 @@ class PlotCanvasLegacy(QtCore.QObject):
 
             if self.big_cursor is False:
                 try:
-                    x, y = self.snap(x_pos, y_pos)
+                    x, y = self.app.geo_editor.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
@@ -622,7 +456,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:
@@ -947,7 +781,6 @@ 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':
@@ -973,11 +806,7 @@ class PlotCanvasLegacy(QtCore.QObject):
 
     def on_mouse_release(self, event):
 
-        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
+        self.is_dragging = False
 
         # Check for middle mouse button release to complete pan procedure
         # Check for middle mouse button press
@@ -1029,7 +858,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)
@@ -1081,59 +910,6 @@ 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):
     """

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

@@ -13,7 +13,6 @@ 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
AppGUI/VisPyData/data/fonts/opensans-regular.ttf → flatcamGUI/VisPyData/data/fonts/opensans-regular.ttf


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


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


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


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


+ 1 - 1
AppGUI/VisPyVisuals.py → flatcamGUI/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 AppGUI.VisPyTesselators import GLUTess
+from flatcamGUI.VisPyTesselators import GLUTess
 
 
 class FlatCAMLineVisual(LineVisual):

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


+ 19 - 0
flatcamGUI/preferences/OptionsGroupUI.py

@@ -0,0 +1,19 @@
+from PyQt5 import QtWidgets
+
+
+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)

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

@@ -5,7 +5,7 @@ from defaults import FlatCAMDefaults
 import logging
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation 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 MainGUI class which constructs the UI
+        :param ui:          reference to the FlatCAMGUI 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 app_obj.new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
+        # def 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,10 +123,16 @@ 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.fill_color_entry,
-            "gerber_plot_line": self.ui.gerber_defaults_form.gerber_gen_group.line_color_entry,
+            "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 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,
@@ -137,6 +143,12 @@ 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,
@@ -168,7 +180,6 @@ 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":
@@ -210,31 +221,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,
@@ -259,117 +270,94 @@ 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_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_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 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_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,
+            "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,
 
             # CutOut Tool
             "tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,
@@ -479,12 +467,6 @@ 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 ####################################################
             # #######################################################################################################
@@ -668,7 +650,7 @@ class PreferencesUIManager:
 
     def show_preferences_gui(self):
         """
-        Called to initialize and show the Preferences AppGUI
+        Called to initialize and show the Preferences GUI
 
         :return: None
         """
@@ -737,7 +719,7 @@ class PreferencesUIManager:
         self.ui.fa_scroll_area.setWidget(fa_form)
         fa_form.show()
 
-        # Initialize the color box's color in Preferences -> Global -> Colors
+        # Initialize the color box's color in Preferences -> Global -> Colo
         self.__init_color_pickers()
 
         # Button handlers
@@ -750,90 +732,167 @@ class PreferencesUIManager:
 
     def __init_color_pickers(self):
         # Init Gerber Plot Colors
-        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
+        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])
 
         # 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.excellon_alpha_entry.set_value(
-            int(self.defaults['excellon_plot_fill'][7:9], 16))
+        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])
 
         # 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.cncjob_alpha_entry.set_value(
-            int(self.defaults['cncjob_travel_fill'][7:9], 16))      # alpha
+        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])
 
         # 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.sl_color_entry.set_value(self.defaults['global_sel_line'])
-
-        self.ui.general_defaults_form.general_gui_group.left_right_alpha_entry.set_value(
+        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])
+
         # 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.right_left_alpha_entry.set_value(
-            int(self.defaults['global_sel_fill'][7:9], 16))
+        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])
 
         # 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 Mouse Cursor color
+        # Init Project Disabled Items 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.")
@@ -862,17 +921,12 @@ class PreferencesUIManager:
             theme = 'white'
 
         should_restart = False
-        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:
+        val = self.ui.general_defaults_form.general_gui_group.theme_radio.get_value()
+        if val != theme:
             msgbox = QtWidgets.QMessageBox()
             msgbox.setText(_("Are you sure you want to continue?"))
-            msgbox.setWindowTitle(_("Application will restart"))
+            msgbox.setWindowTitle(_("Application restart"))
             msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/warning.png'))
-            msgbox.setIcon(QtWidgets.QMessageBox.Question)
 
             bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
             msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.NoRole)
@@ -881,22 +935,15 @@ class PreferencesUIManager:
             msgbox.exec_()
             response = msgbox.clickedButton()
 
-            if theme_new_val != theme:
-                if response == bt_yes:
-                    theme_settings.setValue('theme', theme_new_val)
+            if response == bt_yes:
+                theme_settings.setValue('theme', 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
-                else:
-                    self.ui.general_defaults_form.general_gui_group.theme_radio.set_value(theme)
+                should_restart = True
             else:
-                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)
+                self.ui.general_defaults_form.general_gui_group.theme_radio.set_value(theme)
 
         if save_to_file or should_restart is True:
             self.save_defaults(silent=False)
@@ -920,10 +967,6 @@ 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
@@ -948,14 +991,10 @@ 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))
@@ -996,7 +1035,6 @@ 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
@@ -1033,7 +1071,7 @@ class PreferencesUIManager:
         if self.ui.toolbarfile.isVisible():
             tb_status += 1
 
-        if self.ui.toolbaredit.isVisible():
+        if self.ui.toolbargeo.isVisible():
             tb_status += 2
 
         if self.ui.toolbarview.isVisible():
@@ -1051,7 +1089,7 @@ class PreferencesUIManager:
         if self.ui.grb_edit_toolbar.isVisible():
             tb_status += 64
 
-        if self.ui.status_toolbar.isVisible():
+        if self.ui.snap_toolbar.isVisible():
             tb_status += 128
 
         if self.ui.toolbarshell.isVisible():
@@ -1080,9 +1118,6 @@ 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):
@@ -1118,7 +1153,6 @@ 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
AppGUI/preferences/__init__.py → flatcamGUI/preferences/__init__.py

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

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

@@ -1,10 +1,10 @@
-from PyQt5 import QtWidgets, QtGui
+from PyQt5 import QtWidgets, QtGui, QtCore
 from PyQt5.QtCore import QSettings, Qt
 
-from AppGUI.GUIElements import FCTextArea, FCCheckBox, FCComboBox, FCSpinner, FCColorEntry
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import FCTextArea, FCCheckBox, FCComboBox, FCSpinner, FCEntry
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -158,16 +158,28 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
         self.annotation_color_label.setToolTip(
             _("Set the font color for the annotation texts.")
         )
-        self.annotation_fontcolor_entry = FCColorEntry()
-
+        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)
         grid0.addWidget(self.annotation_color_label, 3, 0)
-        grid0.addWidget(self.annotation_fontcolor_entry, 3, 1)
-
+        grid0.addWidget(color_widget, 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':
@@ -177,3 +189,20 @@ 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

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

@@ -1,10 +1,10 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import QSettings
 
-from AppGUI.GUIElements import FCCheckBox, RadioSet, FCSpinner, FCDoubleSpinner, FCSliderWithSpinner, FCColorEntry
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCSpinner, FCDoubleSpinner, FCEntry
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -170,10 +170,17 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
         self.tline_color_label.setToolTip(
             _("Set the travel line color for plotted objects.")
         )
-        self.tline_color_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.tline_color_label, 14, 0)
-        grid0.addWidget(self.tline_color_entry, 14, 1)
+        grid0.addLayout(self.form_box_child_2, 14, 1)
 
         # Plot Fill Color
         self.tfill_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
@@ -182,20 +189,38 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.tfill_color_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.tfill_color_label, 15, 0)
-        grid0.addWidget(self.tfill_color_entry, 15, 1)
+        grid0.addLayout(self.form_box_child_1, 15, 1)
 
         # Plot Fill Transparency Level
-        self.cncjob_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
-        self.cncjob_alpha_label.setToolTip(
+        self.alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
+        self.alpha_label.setToolTip(
             _("Set the fill transparency for plotted objects.")
         )
-        self.cncjob_alpha_entry = FCSliderWithSpinner(0, 255, 1)
+        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)
 
-        grid0.addWidget(self.cncjob_alpha_label, 16, 0)
-        grid0.addWidget(self.cncjob_alpha_entry, 16, 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)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -203,7 +228,7 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(separator_line, 17, 0, 1, 2)
 
         # CNCJob Object Color
-        self.cnc_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Object Color'))
+        self.cnc_color_label = QtWidgets.QLabel('<b>%s</b>' % _('CNCJob Object Color'))
         grid0.addWidget(self.cnc_color_label, 18, 0, 1, 2)
 
         # Plot Line Color
@@ -211,10 +236,17 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
         self.line_color_label.setToolTip(
             _("Set the color for plotted objects.")
         )
-        self.line_color_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.line_color_label, 19, 0)
-        grid0.addWidget(self.line_color_entry, 19, 1)
+        grid0.addLayout(self.form_box_child_2, 19, 1)
 
         # Plot Fill Color
         self.fill_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
@@ -223,21 +255,32 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.fill_color_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.fill_color_label, 20, 0)
-        grid0.addWidget(self.fill_color_entry, 20, 1)
+        grid0.addLayout(self.form_box_child_1, 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.cncjob_alpha_entry.valueChanged.connect(self.on_cncjob_alpha_changed)  # alpha
+        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.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
@@ -245,12 +288,27 @@ 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_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_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()))
 
-    def on_cncjob_alpha_changed(self, spinner_value):
+        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_tcolor_spinner(self):
+        spinner_value = self.tcolor_alpha_spinner.value()
+        self.tcolor_alpha_slider.setValue(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')
@@ -258,13 +316,74 @@ 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
AppGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py → flatcamGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py

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

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

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

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


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

@@ -1,10 +1,10 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from AppGUI.GUIElements import FCDoubleSpinner, RadioSet, FCCheckBox, NumericalEvalTupleEntry, NumericalEvalEntry
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import FCDoubleSpinner, FCEntry, FloatEntry, RadioSet, FCCheckBox
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation 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 = NumericalEvalTupleEntry(border_color='#0069A9')
+        self.toolchangexy_entry = FCEntry()
 
         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 = NumericalEvalEntry(border_color='#0069A9')
+        self.estartz_entry = FloatEntry()
 
         grid1.addWidget(startzlabel, 2, 0)
         grid1.addWidget(self.estartz_entry, 2, 1)

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

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

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

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

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

@@ -3,10 +3,10 @@ import platform
 from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import QSettings
 
-from AppGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry, FCSliderWithSpinner, FCColorEntry
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -36,31 +36,22 @@ 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, 3)
+        grid1.addWidget(separator_line, 1, 0, 1, 2)
 
         grid2 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid2)
@@ -264,7 +255,7 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
         grid2.addWidget(separator_line, 11, 0, 1, 2)
 
         # Excellon Object Color
-        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Object Color'))
+        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Excellon Object Color'))
         grid2.addWidget(self.gerber_color_label, 12, 0, 1, 2)
 
         # Plot Line Color
@@ -272,10 +263,17 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
         self.line_color_label.setToolTip(
             _("Set the line color for plotted objects.")
         )
-        self.line_color_entry = FCColorEntry()
+        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)
 
         grid2.addWidget(self.line_color_label, 13, 0)
-        grid2.addWidget(self.line_color_entry, 13, 1)
+        grid2.addLayout(self.form_box_child_2, 13, 1)
 
         # Plot Fill Color
         self.fill_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
@@ -284,20 +282,38 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.fill_color_entry = FCColorEntry()
+        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)
 
         grid2.addWidget(self.fill_color_label, 14, 0)
-        grid2.addWidget(self.fill_color_entry, 14, 1)
+        grid2.addLayout(self.form_box_child_1, 14, 1)
 
         # Plot Fill Transparency Level
-        self.excellon_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
-        self.excellon_alpha_label.setToolTip(
+        self.alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
+        self.alpha_label.setToolTip(
             _("Set the fill transparency for plotted objects.")
         )
-        self.excellon_alpha_entry = FCSliderWithSpinner(0, 255, 1)
+        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)
 
-        grid2.addWidget(self.excellon_alpha_label, 15, 0)
-        grid2.addWidget(self.excellon_alpha_entry, 15, 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)
 
         self.layout.addStretch()
 
@@ -317,18 +333,14 @@ 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.excellon_alpha_entry.valueChanged.connect(self.on_excellon_alpha_changed)  # alpha
+        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)
 
         # 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':
@@ -342,12 +354,26 @@ 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_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_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()))
 
-    def on_excellon_alpha_changed(self, spinner_value):
+        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_color_spinner(self):
+        spinner_value = self.color_alpha_spinner.value()
+        self.color_alpha_slider.setValue(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')
@@ -355,6 +381,31 @@ 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')
@@ -362,105 +413,3 @@ 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
AppGUI/preferences/excellon/ExcellonOptPrefGroupUI.py → flatcamGUI/preferences/excellon/ExcellonOptPrefGroupUI.py

@@ -1,12 +1,12 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 
-from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
-    FCComboBox, NumericalEvalTupleEntry
-from AppGUI.preferences import machinist_setting
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
+    FCComboBox
+from flatcamGUI.preferences import machinist_setting
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation 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 = NumericalEvalTupleEntry(border_color='#0069A9')
+        self.endxy_entry = FCEntry()
 
         grid2.addWidget(endmove_xy_label, 9, 0)
         grid2.addWidget(self.endxy_entry, 9, 1)

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

@@ -1,14 +1,14 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-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
+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
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation 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(240)
+        self.excellon_gen_group.setMinimumWidth(220)
         self.excellon_opt_group = ExcellonOptPrefGroupUI(decimals=self.decimals)
         self.excellon_opt_group.setMinimumWidth(290)
         self.excellon_exp_group = ExcellonExpPrefGroupUI(decimals=self.decimals)

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


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

@@ -1,13 +1,13 @@
-from PyQt5 import QtCore, QtWidgets
+from PyQt5 import QtCore, QtWidgets, QtGui
 from PyQt5.QtCore import QSettings
 
-from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, RadioSet, OptionalInputSection, FCSpinner, \
-    FCColorEntry
-from AppGUI.preferences import settings
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, RadioSet, OptionalInputSection, FCSpinner, \
+    FCEntry
+from flatcamGUI.preferences import settings
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -177,6 +177,14 @@ 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)
 
@@ -193,7 +201,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 AppGUI,\n"
+              "The notebook is the collapsible area in the left side of the GUI,\n"
               "and include the Project, Selected and Tool tabs.")
         )
 
@@ -232,8 +240,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 AppGUI\n"
-              "elements that are used in the application.")
+            _("This sets the font size for the Textbox GUI\n"
+              "elements that are used in FlatCAM.")
         )
 
         self.textbox_font_size_spinner = FCSpinner()
@@ -249,29 +257,10 @@ 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, 16, 0, 1, 2)
+        grid0.addWidget(separator_line, 14, 0, 1, 2)
 
         # -----------------------------------------------------------
         # -------------- MOUSE SETTINGS -----------------------------
@@ -334,16 +323,24 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
         self.mouse_color_label.setToolTip(
             _("Set the color of the mouse cursor.")
         )
-        self.mouse_cursor_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.mouse_color_label, 26, 0)
-        grid0.addWidget(self.mouse_cursor_entry, 26, 1)
+        grid0.addLayout(self.form_box_child_1, 26, 1)
 
         self.mois = OptionalInputSection(
             self.mouse_cursor_color_cb,
             [
                 self.mouse_color_label,
-                self.mouse_cursor_entry
+                self.mouse_cursor_entry,
+                self.mouse_cursor_button
             ]
         )
         # Select mouse pan button
@@ -443,7 +440,9 @@ 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:
@@ -462,4 +461,23 @@ 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"]

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

@@ -3,12 +3,12 @@ import sys
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from AppGUI.GUIElements import RadioSet, FCSpinner, FCCheckBox, FCComboBox, FCButton, OptionalInputSection, \
+from flatcamGUI.GUIElements import RadioSet, FCSpinner, FCCheckBox, FCComboBox, FCButton, OptionalInputSection, \
     FCDoubleSpinner
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -380,11 +380,12 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
 
     def on_toggle_shell_from_settings(self, state):
         """
-        Toggle shell ui: if is visible close it, if it is closed then open it
-
+        Toggle shell: 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()

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

@@ -1,11 +1,11 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import QSettings, Qt
 
-from AppGUI.GUIElements import RadioSet, FCCheckBox, FCComboBox, FCSliderWithSpinner, FCColorEntry
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import RadioSet, FCCheckBox, FCButton, FCComboBox, FCEntry, FCSpinner
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation 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 the application.\n"
+            _("Select a theme for FlatCAM.\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 a layout for the application.\n"
+            _("Select an layout for FlatCAM.\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 a style for the application.\n"
+            _("Select an style for FlatCAM.\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 the application.\n"
+            _("Enable High DPI support for FlatCAM.\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 the application objects.\n"
+            _("Enable display of a hover shape for FlatCAM 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 the application objects.\n"
+            _("Enable the display of a selection shape for FlatCAM 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,10 +155,17 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.sl_color_label.setToolTip(
             _("Set the line color for the 'left to right' selection box.")
         )
-        self.sl_color_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.sl_color_label, 16, 0)
-        grid0.addWidget(self.sl_color_entry, 16, 1)
+        grid0.addLayout(self.form_box_child_4, 16, 1)
 
         self.sf_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
         self.sf_color_label.setToolTip(
@@ -167,20 +174,38 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
               "First 6 digits are the color and the last 2\n"
               "digits are for alpha (transparency) level.")
         )
-        self.sf_color_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.sf_color_label, 17, 0)
-        grid0.addWidget(self.sf_color_entry, 17, 1)
+        grid0.addLayout(self.form_box_child_5, 17, 1)
 
         # Plot Selection (left - right) Fill Transparency Level
-        self.left_right_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
-        self.left_right_alpha_label.setToolTip(
+        self.sf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
+        self.sf_alpha_label.setToolTip(
             _("Set the fill transparency for the 'left to right' selection box.")
         )
-        self.left_right_alpha_entry = FCSliderWithSpinner(0, 255, 1)
+        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)
 
-        grid0.addWidget(self.left_right_alpha_label, 18, 0)
-        grid0.addWidget(self.left_right_alpha_entry, 18, 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)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -196,10 +221,17 @@ 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 = FCColorEntry()
+        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)
 
         grid0.addWidget(self.alt_sl_color_label, 21, 0)
-        grid0.addWidget(self.alt_sl_color_entry, 21, 1)
+        grid0.addLayout(self.form_box_child_7, 21, 1)
 
         # Plot Selection (right - left) Fill Color
         self.alt_sf_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
@@ -209,20 +241,38 @@ 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 = FCColorEntry()
+        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)
 
         grid0.addWidget(self.alt_sf_color_label, 22, 0)
-        grid0.addWidget(self.alt_sf_color_entry, 22, 1)
+        grid0.addLayout(self.form_box_child_8, 22, 1)
 
         # Plot Selection (right - left) Fill Transparency Level
-        self.right_left_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
-        self.right_left_alpha_label.setToolTip(
+        self.alt_sf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
+        self.alt_sf_alpha_label.setToolTip(
             _("Set the fill transparency for selection 'right to left' box.")
         )
-        self.right_left_alpha_entry = FCSliderWithSpinner(0, 255, 1)
+        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)
 
-        grid0.addWidget(self.right_left_alpha_label, 23, 0)
-        grid0.addWidget(self.right_left_alpha_entry, 23, 1)
+        grid0.addWidget(self.alt_sf_alpha_label, 23, 0)
+        grid0.addLayout(self.form_box_child_9, 23, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -241,20 +291,34 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.alt_sf_color_label.setToolTip(
             _("Set the color for the shape.")
         )
-        self.draw_color_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.draw_color_label, 26, 0)
-        grid0.addWidget(self.draw_color_entry, 26, 1)
+        grid0.addLayout(self.form_box_child_10, 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 = FCColorEntry()
+        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)
 
         grid0.addWidget(self.sel_draw_color_label, 27, 0)
-        grid0.addWidget(self.sel_draw_color_entry, 27, 1)
+        grid0.addLayout(self.form_box_child_11, 27, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -273,20 +337,34 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.proj_color_label.setToolTip(
             _("Set the color of the items in Project Tab Tree.")
         )
-        self.proj_color_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.proj_color_label, 30, 0)
-        grid0.addWidget(self.proj_color_entry, 30, 1)
+        grid0.addLayout(self.form_box_child_12, 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 = FCColorEntry()
+        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)
 
         grid0.addWidget(self.proj_color_dis_label, 31, 0)
-        grid0.addWidget(self.proj_color_dis_entry, 31, 1)
+        grid0.addLayout(self.form_box_child_13, 31, 1)
 
         # Project autohide CB
         self.project_autohide_cb = FCCheckBox(label=_('Project AutoHide'))
@@ -309,22 +387,32 @@ 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.left_right_alpha_entry.valueChanged.connect(self.on_left_right_alpha_changed)  # alpha
+        self.sl_color_button.clicked.connect(self.on_sl_color_button)
 
         # 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.right_left_alpha_entry.valueChanged.connect(self.on_right_left_alpha_changed)  # alpha
+        self.alt_sl_color_button.clicked.connect(self.on_alt_sl_color_button)
 
         # 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)
 
@@ -349,64 +437,191 @@ 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_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]
+    def on_sf_color_button(self):
+        current_color = QtGui.QColor(self.app.defaults['global_sel_fill'][:7])
 
-    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.
+        c_dialog = QtWidgets.QColorDialog()
+        plot_fill_color = c_dialog.getColor(initial=current_color)
 
-        :param spinner_value:   passed value within [0, 255]
-        :type spinner_value:    int
-        :return:                None
-        :rtype:
-        """
+        if plot_fill_color.isValid() is False:
+            return
+
+        self.sf_color_button.setStyleSheet("background-color:%s" % str(plot_fill_color.name()))
 
+        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
+
+    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_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]
+    def on_alt_sf_color_button(self):
+        current_color = QtGui.QColor(self.app.defaults['global_alt_sel_fill'][:7])
 
-    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.
+        c_dialog = QtWidgets.QColorDialog()
+        plot_fill_color = c_dialog.getColor(initial=current_color)
 
-        :param spinner_value:   passed value within [0, 255]
-        :type spinner_value:    int
-        :return:                None
-        :rtype:
-        """
+        if plot_fill_color.isValid() is False:
+            return
+
+        self.alt_sf_color_button.setStyleSheet("background-color:%s" % str(plot_fill_color.name()))
+
+        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
 
+    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):
         """
@@ -432,13 +647,14 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         # first remove the toolbars:
         try:
             self.app.ui.removeToolBar(self.app.ui.toolbarfile)
-            self.app.ui.removeToolBar(self.app.ui.toolbaredit)
+            self.app.ui.removeToolBar(self.app.ui.toolbargeo)
             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
@@ -449,9 +665,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
             self.app.ui.toolbarfile.setObjectName('File_TB')
             self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbarfile)
 
-            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.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.toolbarshell = QtWidgets.QToolBar('Shell Toolbar')
             self.app.ui.toolbarshell.setObjectName('Shell_TB')
@@ -481,15 +697,22 @@ 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.toolbaredit = QtWidgets.QToolBar('Edit Toolbar')
-            self.app.ui.toolbaredit.setObjectName('Edit_TB')
-            self.app.ui.addToolBar(self.app.ui.toolbaredit)
+            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.toolbarview = QtWidgets.QToolBar('View Toolbar')
             self.app.ui.toolbarview.setObjectName('View_TB')
@@ -520,9 +743,18 @@ 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)
@@ -535,9 +767,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.app.connect_toolbar_signals()
 
         self.app.ui.grid_snap_btn.setChecked(True)
-
-        self.app.ui.corner_snap_btn.setVisible(False)
-        self.app.ui.snap_magnet.setVisible(False)
+        self.app.ui.on_grid_snap_triggered(state=True)
 
         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
AppGUI/preferences/general/GeneralPreferencesUI.py → flatcamGUI/preferences/general/GeneralPreferencesUI.py

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

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


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

@@ -1,12 +1,11 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel, NumericalEvalTupleEntry, \
-    NumericalEvalEntry
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import FCEntry, FloatEntry, FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -47,9 +46,8 @@ 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
@@ -58,9 +56,8 @@ 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
@@ -189,11 +186,6 @@ 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 ----------
         # -----------------------------
@@ -203,10 +195,10 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
               "Those parameters are available only for\n"
               "Advanced App. Level.")
         )
-        grid1.addWidget(self.adv_label, 13, 0, 1, 2)
+        grid1.addWidget(self.adv_label, 12, 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"
@@ -214,7 +206,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
                 "is forbidden."
             )
         )
-        grid1.addWidget(self.exclusion_cb, 14, 0, 1, 2)
+        grid1.addWidget(self.exclusion_cb, 13, 0, 1, 2)
 
         # Area Selection shape
         self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
@@ -225,8 +217,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
                                           {'label': _("Polygon"), 'value': 'polygon'}])
 
-        grid1.addWidget(self.area_shape_label, 15, 0)
-        grid1.addWidget(self.area_shape_radio, 15, 1)
+        grid1.addWidget(self.area_shape_label, 14, 0)
+        grid1.addWidget(self.area_shape_radio, 14, 1)
 
         # Chose Strategy
         self.strategy_label = FCLabel('%s:' % _("Strategy"))
@@ -237,8 +229,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
                                         {'label': _('Around'), 'value': 'around'}])
 
-        grid1.addWidget(self.strategy_label, 16, 0)
-        grid1.addWidget(self.strategy_radio, 16, 1)
+        grid1.addWidget(self.strategy_label, 15, 0)
+        grid1.addWidget(self.strategy_radio, 15, 1)
 
         # Over Z
         self.over_z_label = FCLabel('%s:' % _("Over Z"))

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

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

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

@@ -1,11 +1,11 @@
-from PyQt5 import QtWidgets
+from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5.QtCore import QSettings
 
-from AppGUI.GUIElements import FCCheckBox, FCSpinner, FCEntry, FCColorEntry
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, FCEntry
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -31,22 +31,12 @@ 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.")
         )
-        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)
+        self.layout.addWidget(self.plot_cb)
 
         grid0 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid0)
@@ -87,7 +77,7 @@ class GeometryGenPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(separator_line, 9, 0, 1, 2)
 
         # Geometry Object Color
-        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Object Color'))
+        self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Geometry Object Color'))
         grid0.addWidget(self.gerber_color_label, 10, 0, 1, 2)
 
         # Plot Line Color
@@ -95,15 +85,39 @@ class GeometryGenPrefGroupUI(OptionsGroupUI):
         self.line_color_label.setToolTip(
             _("Set the line color for plotted objects.")
         )
-        self.line_color_entry = FCColorEntry()
+        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)
 
         grid0.addWidget(self.line_color_label, 11, 0)
-        grid0.addWidget(self.line_color_entry, 11, 1)
+        grid0.addLayout(self.form_box_child_2, 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)

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

@@ -1,13 +1,12 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 
-from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCSpinner, FCComboBox, \
-    NumericalEvalTupleEntry
-from AppGUI.preferences import machinist_setting
-from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
+from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCEntry, FCSpinner, FCComboBox
+from flatcamGUI.preferences import machinist_setting
+from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
-import AppTranslation as fcTranslate
+import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -177,7 +176,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 = NumericalEvalTupleEntry(border_color='#0069A9')
+        self.endxy_entry = FCEntry()
 
         grid1.addWidget(endmove_xy_label, 7, 0)
         grid1.addWidget(self.endxy_entry, 7, 1)

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません