Просмотр исходного кода

- 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

Marius Stanciu 5 лет назад
Родитель
Сommit
c27a2d29e7

+ 9 - 6
AppGUI/GUIElements.py

@@ -570,10 +570,13 @@ class FCEntry3(FCEntry):
 
 
 
 
 class EvalEntry(QtWidgets.QLineEdit):
 class EvalEntry(QtWidgets.QLineEdit):
-    def __init__(self, parent=None):
+    def __init__(self, border_color=None, parent=None):
         super(EvalEntry, self).__init__(parent)
         super(EvalEntry, self).__init__(parent)
         self.readyToEdit = True
         self.readyToEdit = True
 
 
+        if border_color:
+            self.setStyleSheet("QLineEdit {border: 1px solid %s;}" % border_color)
+
         self.editingFinished.connect(self.on_edit_finished)
         self.editingFinished.connect(self.on_edit_finished)
 
 
     def on_edit_finished(self):
     def on_edit_finished(self):
@@ -639,7 +642,7 @@ class EvalEntry2(QtWidgets.QLineEdit):
 
 
     def get_value(self):
     def get_value(self):
         raw = str(self.text()).strip(' ')
         raw = str(self.text()).strip(' ')
-        evaled = 0.0
+
         try:
         try:
             evaled = eval(raw)
             evaled = eval(raw)
         except Exception as e:
         except Exception as e:
@@ -660,8 +663,8 @@ class NumericalEvalEntry(EvalEntry):
     """
     """
     Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
     Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
     """
     """
-    def __init__(self):
-        super().__init__()
+    def __init__(self, border_color=None):
+        super().__init__(border_color=border_color)
 
 
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s]*")
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s]*")
         validator = QtGui.QRegExpValidator(regex, self)
         validator = QtGui.QRegExpValidator(regex, self)
@@ -672,8 +675,8 @@ class NumericalEvalTupleEntry(EvalEntry):
     """
     """
     Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
     Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
     """
     """
-    def __init__(self):
-        super().__init__()
+    def __init__(self, border_color=None):
+        super().__init__(border_color=border_color)
 
 
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s\,]*")
         regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s\,]*")
         validator = QtGui.QRegExpValidator(regex, self)
         validator = QtGui.QRegExpValidator(regex, self)

+ 28 - 265
AppGUI/ObjectUI.py

@@ -114,7 +114,7 @@ class ObjectUI(QtWidgets.QWidget):
             self.common_grid.addWidget(self.transform_label, 2, 0, 1, 2)
             self.common_grid.addWidget(self.transform_label, 2, 0, 1, 2)
 
 
             # ### Scale ####
             # ### Scale ####
-            self.scale_entry = NumericalEvalEntry()
+            self.scale_entry = NumericalEvalEntry(border_color='#0069A9')
             self.scale_entry.set_value(1.0)
             self.scale_entry.set_value(1.0)
             self.scale_entry.setToolTip(
             self.scale_entry.setToolTip(
                 _("Factor by which to multiply\n"
                 _("Factor by which to multiply\n"
@@ -132,7 +132,7 @@ class ObjectUI(QtWidgets.QWidget):
             self.common_grid.addWidget(self.scale_button, 3, 1)
             self.common_grid.addWidget(self.scale_button, 3, 1)
 
 
             # ### Offset ####
             # ### Offset ####
-            self.offsetvector_entry = NumericalEvalTupleEntry()
+            self.offsetvector_entry = NumericalEvalTupleEntry(border_color='#0069A9')
             self.offsetvector_entry.setText("(0.0, 0.0)")
             self.offsetvector_entry.setText("(0.0, 0.0)")
             self.offsetvector_entry.setToolTip(
             self.offsetvector_entry.setToolTip(
                 _("Amount by which to move the object\n"
                 _("Amount by which to move the object\n"
@@ -206,15 +206,17 @@ class GerberObjectUI(ObjectUI):
         grid0.addWidget(self.multicolored_cb, 0, 2)
         grid0.addWidget(self.multicolored_cb, 0, 2)
 
 
         # Plot CB
         # Plot CB
-        self.plot_cb = FCCheckBox('%s' % _("Plot"))
-        # self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
-        self.plot_cb.setToolTip(_("Plot (show) this object."))
+        self.plot_lbl = FCLabel('%s' % _("Plot"))
+        self.plot_lbl.setToolTip(_("Plot (show) this object."))
+        self.plot_cb = FCCheckBox()
 
 
-        grid0.addWidget(self.plot_cb, 1, 0, 1, 2)
+        grid0.addWidget(self.plot_lbl, 1, 0)
+        grid0.addWidget(self.plot_cb, 1, 1)
 
 
         # ## Object name
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(self.name_hlay)
         self.custom_box.addLayout(self.name_hlay)
+
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
@@ -225,7 +227,7 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(hlay_plot)
         self.custom_box.addLayout(hlay_plot)
 
 
         # ### Gerber Apertures ####
         # ### Gerber Apertures ####
-        self.apertures_table_label = QtWidgets.QLabel('<b>%s:</b>' % _('Apertures'))
+        self.apertures_table_label = QtWidgets.QLabel('%s:' % _('Apertures'))
         self.apertures_table_label.setToolTip(
         self.apertures_table_label.setToolTip(
             _("Apertures Table for the Gerber Object.")
             _("Apertures Table for the Gerber Object.")
         )
         )
@@ -282,253 +284,21 @@ class GerberObjectUI(ObjectUI):
         # start with apertures table hidden
         # start with apertures table hidden
         self.apertures_table.setVisible(False)
         self.apertures_table.setVisible(False)
 
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.custom_box.addWidget(separator_line)
-
-        # Isolation Routing
-        self.isolation_routing_label = QtWidgets.QLabel("<b>%s</b>" % _("Isolation Routing"))
-        self.isolation_routing_label.setToolTip(
-            _("Create a Geometry object with\n"
-              "toolpaths to cut outside polygons.")
-        )
-        self.custom_box.addWidget(self.isolation_routing_label)
-
-        # ###########################################
-        # ########## NEW GRID #######################
-        # ###########################################
-
-        grid1 = QtWidgets.QGridLayout()
-        self.custom_box.addLayout(grid1)
-        grid1.setColumnStretch(0, 0)
-        grid1.setColumnStretch(1, 1)
-        grid1.setColumnStretch(2, 1)
-
-        # Tool Type
-        self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
-        self.tool_type_label.setToolTip(
-            _("Choose which tool to use for Gerber isolation:\n"
-              "'Circular' or 'V-shape'.\n"
-              "When the 'V-shape' is selected then the tool\n"
-              "diameter will depend on the chosen cut depth.")
-        )
-        self.tool_type_radio = RadioSet([{'label': _('Circular'), 'value': 'circular'},
-                                         {'label': _('V-Shape'), 'value': 'v'}])
-
-        grid1.addWidget(self.tool_type_label, 0, 0)
-        grid1.addWidget(self.tool_type_radio, 0, 1, 1, 2)
-
-        # Tip Dia
-        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
-        self.tipdialabel.setToolTip(
-            _("The tip diameter for V-Shape Tool")
-        )
-        self.tipdia_spinner = FCDoubleSpinner(callback=self.confirmation_message)
-        self.tipdia_spinner.set_range(-99.9999, 99.9999)
-        self.tipdia_spinner.set_precision(self.decimals)
-        self.tipdia_spinner.setSingleStep(0.1)
-        self.tipdia_spinner.setWrapping(True)
-        grid1.addWidget(self.tipdialabel, 1, 0)
-        grid1.addWidget(self.tipdia_spinner, 1, 1, 1, 2)
-
-        # Tip Angle
-        self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
-        self.tipanglelabel.setToolTip(
-            _("The tip angle for V-Shape Tool.\n"
-              "In degree.")
-        )
-        self.tipangle_spinner = FCDoubleSpinner(callback=self.confirmation_message)
-        self.tipangle_spinner.set_range(1, 180)
-        self.tipangle_spinner.set_precision(self.decimals)
-        self.tipangle_spinner.setSingleStep(5)
-        self.tipangle_spinner.setWrapping(True)
-        grid1.addWidget(self.tipanglelabel, 2, 0)
-        grid1.addWidget(self.tipangle_spinner, 2, 1, 1, 2)
-
-        # Cut Z
-        self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
-        self.cutzlabel.setToolTip(
-            _("Cutting depth (negative)\n"
-              "below the copper surface.")
-        )
-        self.cutz_spinner = FCDoubleSpinner(callback=self.confirmation_message)
-        self.cutz_spinner.set_range(-9999.9999, 0.0000)
-        self.cutz_spinner.set_precision(self.decimals)
-        self.cutz_spinner.setSingleStep(0.1)
-        self.cutz_spinner.setWrapping(True)
-        grid1.addWidget(self.cutzlabel, 3, 0)
-        grid1.addWidget(self.cutz_spinner, 3, 1, 1, 2)
-
-        # Tool diameter
-        tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
-        tdlabel.setToolTip(
-            _("Diameter of the cutting tool.\n"
-              "If you want to have an isolation path\n"
-              "inside the actual shape of the Gerber\n"
-              "feature, use a negative value for\n"
-              "this parameter.")
-        )
-        tdlabel.setMinimumWidth(90)
-        self.iso_tool_dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.iso_tool_dia_entry.set_range(-9999.9999, 9999.9999)
-        self.iso_tool_dia_entry.set_precision(self.decimals)
-        self.iso_tool_dia_entry.setSingleStep(0.1)
-
-        grid1.addWidget(tdlabel, 4, 0)
-        grid1.addWidget(self.iso_tool_dia_entry, 4, 1, 1, 2)
-
-        # Number of Passes
-        passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
-        passlabel.setToolTip(
-            _("Width of the isolation gap in\n"
-              "number (integer) of tool widths.")
-        )
-        passlabel.setMinimumWidth(90)
-        self.iso_width_entry = FCSpinner(callback=self.confirmation_message_int)
-        self.iso_width_entry.set_range(1, 999)
-
-        grid1.addWidget(passlabel, 5, 0)
-        grid1.addWidget(self.iso_width_entry, 5, 1, 1, 2)
-
-        # Pass overlap
-        overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
-        overlabel.setToolTip(
-            _("How much (percentage) of the tool width to overlap each tool pass.")
-        )
-        overlabel.setMinimumWidth(90)
-        self.iso_overlap_entry = FCDoubleSpinner(suffix='%', callback=self.confirmation_message)
-        self.iso_overlap_entry.set_precision(self.decimals)
-        self.iso_overlap_entry.setWrapping(True)
-        self.iso_overlap_entry.set_range(0.0000, 99.9999)
-        self.iso_overlap_entry.setSingleStep(0.1)
-        grid1.addWidget(overlabel, 6, 0)
-        grid1.addWidget(self.iso_overlap_entry, 6, 1, 1, 2)
-
-        # Milling Type Radio Button
-        self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
-        self.milling_type_label.setToolTip(
-            _("Milling type:\n"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conventional'), 'value': 'cv'}])
-        grid1.addWidget(self.milling_type_label, 7, 0)
-        grid1.addWidget(self.milling_type_radio, 7, 1, 1, 2)
-
-        # combine all passes CB
-        self.combine_passes_cb = FCCheckBox(label=_('Combine'))
-        self.combine_passes_cb.setToolTip(
-            _("Combine all passes into one object")
-        )
-
         # generate follow
         # generate follow
-        self.follow_cb = FCCheckBox(label=_('"Follow"'))
-        self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
-                                    "This means that it will cut through\n"
-                                    "the middle of the trace."))
-
-        # avoid an area from isolation
-        self.except_cb = FCCheckBox(label=_('Except'))
-        self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
-                                    "by checking this, the area of the object below\n"
-                                    "will be subtracted from the isolation geometry."))
-        grid1.addWidget(self.combine_passes_cb, 8, 0)
-        grid1.addWidget(self.follow_cb, 8, 1)
-        grid1.addWidget(self.except_cb, 8, 2)
-
-        # ## Form Layout
-        form_layout = QtWidgets.QFormLayout()
-        grid1.addLayout(form_layout, 9, 0, 1, 3)
-
-        # ################################################
-        # ##### Type of object to be excepted ############
-        # ################################################
-        self.type_obj_combo = FCComboBox()
-        self.type_obj_combo.addItems([_("Gerber"), _("Geometry")])
-
-        # we get rid of item1 ("Excellon") as it is not suitable
-        # self.type_obj_combo.view().setRowHidden(1, True)
-        self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.resource_loc + "/flatcam_icon16.png"))
-        self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.resource_loc + "/geometry16.png"))
-
-        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Obj Type"))
-        self.type_obj_combo_label.setToolTip(
-            _("Specify the type of object to be excepted from isolation.\n"
-              "It can be of type: Gerber or Geometry.\n"
-              "What is selected here will dictate the kind\n"
-              "of objects that will populate the 'Object' combobox.")
-        )
-        # self.type_obj_combo_label.setMinimumWidth(60)
-        form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
-
-        # ################################################
-        # ##### The object to be excepted ################
-        # ################################################
-        self.obj_combo = FCComboBox()
-
-        self.obj_label = QtWidgets.QLabel('%s:' % _("Object"))
-        self.obj_label.setToolTip(_("Object whose area will be removed from isolation geometry."))
-
-        form_layout.addRow(self.obj_label, self.obj_combo)
-
-        # ---------------------------------------------- #
-        # --------- Isolation scope -------------------- #
-        # ---------------------------------------------- #
-        self.iso_scope_label = QtWidgets.QLabel('<b>%s:</b>' % _('Scope'))
-        self.iso_scope_label.setToolTip(
-            _("Isolation scope. Choose what to isolate:\n"
-              "- 'All' -> Isolate all the polygons in the object\n"
-              "- 'Selection' -> Isolate a selection of polygons.")
-        )
-        self.iso_scope_radio = RadioSet([{'label': _('All'), 'value': 'all'},
-                                         {'label': _('Selection'), 'value': 'single'}])
-
-        grid1.addWidget(self.iso_scope_label, 10, 0)
-        grid1.addWidget(self.iso_scope_radio, 10, 1, 1, 2)
-
-        # ---------------------------------------------- #
-        # --------- Isolation type  -------------------- #
-        # ---------------------------------------------- #
-        self.iso_type_label = QtWidgets.QLabel('<b>%s:</b>' % _('Isolation Type'))
-        self.iso_type_label.setToolTip(
-            _("Choose how the isolation will be executed:\n"
-              "- 'Full' -> complete isolation of polygons\n"
-              "- 'Ext' -> will isolate only on the outside\n"
-              "- 'Int' -> will isolate only on the inside\n"
-              "'Exterior' isolation is almost always possible\n"
-              "(with the right tool) but 'Interior'\n"
-              "isolation can be done only when there is an opening\n"
-              "inside of the polygon (e.g polygon is a 'doughnut' shape).")
-        )
-        self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
-                                        {'label': _('Ext'), 'value': 'ext'},
-                                        {'label': _('Int'), 'value': 'int'}])
-
-        grid1.addWidget(self.iso_type_label, 11, 0)
-        grid1.addWidget(self.iso_type_radio, 11, 1, 1, 2)
-
-        self.generate_iso_button = QtWidgets.QPushButton("%s" % _("Generate Isolation Geometry"))
-        self.generate_iso_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.generate_iso_button.setToolTip(
-            _("Create a Geometry object with toolpaths to cut \n"
-              "isolation outside, inside or on both sides of the\n"
-              "object. For a Gerber object outside means outside\n"
-              "of the Gerber feature and inside means inside of\n"
-              "the Gerber feature, if possible at all. This means\n"
-              "that only if the Gerber feature has openings inside, they\n"
-              "will be isolated. If what is wanted is to cut isolation\n"
-              "inside the actual Gerber feature, use a negative tool\n"
-              "diameter above.")
-        )
-        grid1.addWidget(self.generate_iso_button, 12, 0, 1, 3)
-
+        self.follow_lbl = FCLabel('%s:' % _("Follow"))
+        self.follow_lbl.setToolTip(_("Generate a 'Follow' geometry.\n"
+                                     "This means that it will cut through\n"
+                                     "the middle of the trace."))
+        self.follow_lbl.setMinimumWidth(90)
+        self.follow_cb = FCCheckBox()
+
+        hf_lay = QtWidgets.QHBoxLayout()
+        self.custom_box.addLayout(hf_lay)
+        hf_lay.addWidget(self.follow_lbl)
+        hf_lay.addWidget(self.follow_cb)
+        hf_lay.addStretch()
+
+        # Buffer Geometry
         self.create_buffer_button = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
         self.create_buffer_button = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
         self.create_buffer_button.setToolTip(
         self.create_buffer_button.setToolTip(
             _("This button is shown only when the Gerber file\n"
             _("This button is shown only when the Gerber file\n"
@@ -536,19 +306,12 @@ class GerberObjectUI(ObjectUI):
               "Clicking this will create the buffered geometry\n"
               "Clicking this will create the buffered geometry\n"
               "required for isolation.")
               "required for isolation.")
         )
         )
-        grid1.addWidget(self.create_buffer_button, 13, 0, 1, 3)
-
-        self.ohis_iso = OptionalHideInputSection(
-            self.except_cb,
-            [self.type_obj_combo, self.type_obj_combo_label, self.obj_combo, self.obj_label],
-            logic=True
-        )
+        self.custom_box.addWidget(self.create_buffer_button)
 
 
-        separator_line2 = QtWidgets.QFrame()
-        separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid1.addWidget(separator_line2, 14, 0, 1, 3)
-        # grid1.addWidget(QtWidgets.QLabel(''), 15, 0)
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.custom_box.addWidget(separator_line)
 
 
         # ###########################################
         # ###########################################
         # ########## NEW GRID #######################
         # ########## NEW GRID #######################

+ 5 - 4
AppGUI/preferences/PreferencesUIManager.py

@@ -330,9 +330,9 @@ class PreferencesUIManager:
             "tools_iso_tooldia":        self.ui.tools_defaults_form.tools_iso_group.tool_dia_entry,
             "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_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_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_spinner,
-            "tools_iso_tool_vtipangle": self.ui.tools_defaults_form.tools_iso_group.tipangle_spinner,
-            "tools_iso_tool_cutz":      self.ui.tools_defaults_form.tools_iso_group.cutz_spinner,
+            "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_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_passes":         self.ui.tools_defaults_form.tools_iso_group.passes_entry,
@@ -346,6 +346,7 @@ class PreferencesUIManager:
             "tools_iso_isoexcept":      self.ui.tools_defaults_form.tools_iso_group.except_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_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_area_shape":     self.ui.tools_defaults_form.tools_iso_group.area_shape_radio,
+            "tools_iso_plotting":       self.ui.tools_defaults_form.tools_iso_group.plotting_radio,
 
 
             # NCC Tool
             # NCC Tool
             "tools_ncctools":           self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
             "tools_ncctools":           self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
@@ -360,13 +361,13 @@ class PreferencesUIManager:
             "tools_ncc_offset_value":   self.ui.tools_defaults_form.tools_ncc_group.ncc_offset_spinner,
             "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_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_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_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_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_ncccutz":            self.ui.tools_defaults_form.tools_ncc_group.cutz_entry,
             "tools_ncctipdia":          self.ui.tools_defaults_form.tools_ncc_group.tipdia_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_ncctipangle":        self.ui.tools_defaults_form.tools_ncc_group.tipangle_entry,
             "tools_nccnewdia":          self.ui.tools_defaults_form.tools_ncc_group.newdia_entry,
             "tools_nccnewdia":          self.ui.tools_defaults_form.tools_ncc_group.newdia_entry,
+            "tools_ncc_plotting":       self.ui.tools_defaults_form.tools_ncc_group.plotting_radio,
 
 
             # CutOut Tool
             # CutOut Tool
             "tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,
             "tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,

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

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

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

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

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

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

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

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

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

@@ -63,85 +63,6 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         grid0.addWidget(separator_line, 2, 0, 1, 2)
         grid0.addWidget(separator_line, 2, 0, 1, 2)
 
 
-        # Tool Type
-        self.tool_type_label = QtWidgets.QLabel('<b>%s</b>' % _('Tool Type'))
-        self.tool_type_label.setToolTip(
-            _("Choose which tool to use for Gerber isolation:\n"
-              "'Circular' or 'V-shape'.\n"
-              "When the 'V-shape' is selected then the tool\n"
-              "diameter will depend on the chosen cut depth.")
-        )
-        self.tool_type_radio = RadioSet([{'label': 'Circular', 'value': 'circular'},
-                                         {'label': 'V-Shape', 'value': 'v'}])
-
-        grid0.addWidget(self.tool_type_label, 3, 0)
-        grid0.addWidget(self.tool_type_radio, 3, 1, 1, 2)
-
-        # Tip Dia
-        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
-        self.tipdialabel.setToolTip(
-            _("The tip diameter for V-Shape Tool")
-        )
-        self.tipdia_spinner = FCDoubleSpinner()
-        self.tipdia_spinner.set_precision(self.decimals)
-        self.tipdia_spinner.set_range(-99.9999, 99.9999)
-        self.tipdia_spinner.setSingleStep(0.1)
-        self.tipdia_spinner.setWrapping(True)
-        grid0.addWidget(self.tipdialabel, 4, 0)
-        grid0.addWidget(self.tipdia_spinner, 4, 1, 1, 2)
-
-        # Tip Angle
-        self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
-        self.tipanglelabel.setToolTip(
-            _("The tip angle for V-Shape Tool.\n"
-              "In degree.")
-        )
-        self.tipangle_spinner = FCSpinner()
-        self.tipangle_spinner.set_range(1, 180)
-        self.tipangle_spinner.set_step(5)
-        self.tipangle_spinner.setWrapping(True)
-        grid0.addWidget(self.tipanglelabel, 5, 0)
-        grid0.addWidget(self.tipangle_spinner, 5, 1, 1, 2)
-
-        # Cut Z
-        self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
-        self.cutzlabel.setToolTip(
-            _("Cutting depth (negative)\n"
-              "below the copper surface.")
-        )
-        self.cutz_spinner = FCDoubleSpinner()
-        self.cutz_spinner.set_precision(self.decimals)
-        self.cutz_spinner.set_range(-99.9999, 0.0000)
-        self.cutz_spinner.setSingleStep(0.1)
-        self.cutz_spinner.setWrapping(True)
-
-        grid0.addWidget(self.cutzlabel, 6, 0)
-        grid0.addWidget(self.cutz_spinner, 6, 1, 1, 2)
-
-        # Isolation Type
-        self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
-        self.iso_type_label.setToolTip(
-            _("Choose how the isolation will be executed:\n"
-              "- 'Full' -> complete isolation of polygons\n"
-              "- 'Ext' -> will isolate only on the outside\n"
-              "- 'Int' -> will isolate only on the inside\n"
-              "'Exterior' isolation is almost always possible\n"
-              "(with the right tool) but 'Interior'\n"
-              "isolation can be done only when there is an opening\n"
-              "inside of the polygon (e.g polygon is a 'doughnut' shape).")
-        )
-        self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
-                                        {'label': _('Exterior'), 'value': 'ext'},
-                                        {'label': _('Interior'), 'value': 'int'}])
-
-        grid0.addWidget(self.iso_type_label, 7, 0,)
-        grid0.addWidget(self.iso_type_radio, 7, 1, 1, 2)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 8, 0, 1, 2)
-
         # Buffering Type
         # Buffering Type
         buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
         buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
         buffering_label.setToolTip(
         buffering_label.setToolTip(

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

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

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

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

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

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

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

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

+ 29 - 29
AppGUI/preferences/tools/ToolsISOPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 from PyQt5.QtCore import QSettings
 
 
-from AppGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner
+from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, NumericalEvalTupleEntry
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 
 import gettext
 import gettext
@@ -32,7 +32,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
             _("Create a Geometry object with\n"
             _("Create a Geometry object with\n"
               "toolpaths to cut around polygons.")
               "toolpaths to cut around polygons.")
         )
         )
-        self.layout.addWidget(self.clearcopper_label)
+        self.layout.addWidget(self.iso_label)
 
 
         grid0 = QtWidgets.QGridLayout()
         grid0 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid0)
         self.layout.addLayout(grid0)
@@ -44,11 +44,11 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
               "The value of the diameter has to use the dot decimals separator.\n"
               "The value of the diameter has to use the dot decimals separator.\n"
               "Valid values: 0.3, 1.0")
               "Valid values: 0.3, 1.0")
         )
         )
-        self.tool_dia_entry = FCEntry(border_color='#0069A9')
+        self.tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
         self.tool_dia_entry.setPlaceholderText(_("Comma separated values"))
         self.tool_dia_entry.setPlaceholderText(_("Comma separated values"))
 
 
         grid0.addWidget(isotdlabel, 0, 0)
         grid0.addWidget(isotdlabel, 0, 0)
-        grid0.addWidget(self.tool_dia_entry, 0, 1)
+        grid0.addWidget(self.tool_dia_entry, 0, 1, 1, 2)
 
 
         # Tool order Radio Button
         # Tool order Radio Button
         self.order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
         self.order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
@@ -64,7 +64,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
                                      {'label': _('Reverse'), 'value': 'rev'}])
                                      {'label': _('Reverse'), 'value': 'rev'}])
 
 
         grid0.addWidget(self.order_label, 1, 0)
         grid0.addWidget(self.order_label, 1, 0)
-        grid0.addWidget(self.order_radio, 1, 1)
+        grid0.addWidget(self.order_radio, 1, 1, 1, 2)
 
 
         # Tool Type Radio Button
         # Tool Type Radio Button
         self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
         self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
@@ -83,7 +83,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         )
         )
 
 
         grid0.addWidget(self.tool_type_label, 2, 0)
         grid0.addWidget(self.tool_type_label, 2, 0)
-        grid0.addWidget(self.tool_type_radio, 2, 1)
+        grid0.addWidget(self.tool_type_radio, 2, 1, 1, 2)
 
 
         # Tip Dia
         # Tip Dia
         self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
         self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
@@ -95,7 +95,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.tipdia_entry.setSingleStep(0.1)
         self.tipdia_entry.setSingleStep(0.1)
 
 
         grid0.addWidget(self.tipdialabel, 3, 0)
         grid0.addWidget(self.tipdialabel, 3, 0)
-        grid0.addWidget(self.tipdia_entry, 3, 1)
+        grid0.addWidget(self.tipdia_entry, 3, 1, 1, 2)
 
 
         # Tip Angle
         # Tip Angle
         self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
         self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
@@ -109,7 +109,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.tipangle_entry.setWrapping(True)
         self.tipangle_entry.setWrapping(True)
 
 
         grid0.addWidget(self.tipanglelabel, 4, 0)
         grid0.addWidget(self.tipanglelabel, 4, 0)
-        grid0.addWidget(self.tipangle_entry, 4, 1)
+        grid0.addWidget(self.tipangle_entry, 4, 1, 1, 2)
 
 
         # Cut Z entry
         # Cut Z entry
         cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
         cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
@@ -128,7 +128,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         )
         )
 
 
         grid0.addWidget(cutzlabel, 5, 0)
         grid0.addWidget(cutzlabel, 5, 0)
-        grid0.addWidget(self.cutz_entry, 5, 1)
+        grid0.addWidget(self.cutz_entry, 5, 1, 1, 2)
 
 
         # New Diameter
         # New Diameter
         self.newdialabel = QtWidgets.QLabel('%s:' % _('New Dia'))
         self.newdialabel = QtWidgets.QLabel('%s:' % _('New Dia'))
@@ -143,12 +143,12 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.newdia_entry.setSingleStep(0.1)
         self.newdia_entry.setSingleStep(0.1)
 
 
         grid0.addWidget(self.newdialabel, 6, 0)
         grid0.addWidget(self.newdialabel, 6, 0)
-        grid0.addWidget(self.newdia_entry, 6, 1)
+        grid0.addWidget(self.newdia_entry, 6, 1, 1, 2)
 
 
         separator_line = QtWidgets.QFrame()
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 7, 0, 1, 2)
+        grid0.addWidget(separator_line, 7, 0, 1, 3)
 
 
         # Passes
         # Passes
         passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
         passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
@@ -161,7 +161,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.passes_entry.setObjectName("i_passes")
         self.passes_entry.setObjectName("i_passes")
 
 
         grid0.addWidget(passlabel, 8, 0)
         grid0.addWidget(passlabel, 8, 0)
-        grid0.addWidget(self.passes_entry, 8, 1)
+        grid0.addWidget(self.passes_entry, 8, 1, 1, 2)
 
 
         # Overlap Entry
         # Overlap Entry
         overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
         overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
@@ -176,7 +176,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.overlap_entry.setObjectName("i_overlap")
         self.overlap_entry.setObjectName("i_overlap")
 
 
         grid0.addWidget(overlabel, 9, 0)
         grid0.addWidget(overlabel, 9, 0)
-        grid0.addWidget(self.overlap_entry, 9, 1)
+        grid0.addWidget(self.overlap_entry, 9, 1, 1, 2)
 
 
         # Milling Type Radio Button
         # Milling Type Radio Button
         self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
         self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
@@ -195,7 +195,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         )
         )
 
 
         grid0.addWidget(self.milling_type_label, 10, 0)
         grid0.addWidget(self.milling_type_label, 10, 0)
-        grid0.addWidget(self.milling_type_radio, 10, 1)
+        grid0.addWidget(self.milling_type_radio, 10, 1, 1, 2)
 
 
         # Follow
         # Follow
         self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
         self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
@@ -212,7 +212,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.follow_cb.setObjectName("i_follow")
         self.follow_cb.setObjectName("i_follow")
 
 
         grid0.addWidget(self.follow_label, 11, 0)
         grid0.addWidget(self.follow_label, 11, 0)
-        grid0.addWidget(self.follow_cb, 11, 1)
+        grid0.addWidget(self.follow_cb, 11, 1, 1, 2)
 
 
         # Isolation Type
         # Isolation Type
         self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
         self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
@@ -232,15 +232,15 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.iso_type_radio.setObjectName("i_type")
         self.iso_type_radio.setObjectName("i_type")
 
 
         grid0.addWidget(self.iso_type_label, 12, 0)
         grid0.addWidget(self.iso_type_label, 12, 0)
-        grid0.addWidget(self.iso_type_radio, 12, 1)
+        grid0.addWidget(self.iso_type_radio, 12, 1, 1, 2)
 
 
         separator_line = QtWidgets.QFrame()
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 13, 0, 1, 2)
+        grid0.addWidget(separator_line, 13, 0, 1, 3)
 
 
         # Rest machining CheckBox
         # Rest machining CheckBox
-        self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
+        self.rest_cb = FCCheckBox('%s' % _("Rest"))
         self.rest_cb.setObjectName("i_rest_machining")
         self.rest_cb.setObjectName("i_rest_machining")
         self.rest_cb.setToolTip(
         self.rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
             _("If checked, use 'rest machining'.\n"
@@ -252,7 +252,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
               "If not checked, use the standard algorithm.")
               "If not checked, use the standard algorithm.")
         )
         )
 
 
-        grid0.addWidget(self.ncc_rest_cb, 17, 0, 1, 2)
+        grid0.addWidget(self.rest_cb, 17, 0)
 
 
         # Combine All Passes
         # Combine All Passes
         self.combine_passes_cb = FCCheckBox(label=_('Combine'))
         self.combine_passes_cb = FCCheckBox(label=_('Combine'))
@@ -261,7 +261,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         )
         )
         self.combine_passes_cb.setObjectName("i_combine")
         self.combine_passes_cb.setObjectName("i_combine")
 
 
-        grid0.addWidget(self.combine_passes_cb, 18, 0, 1, 2)
+        grid0.addWidget(self.combine_passes_cb, 17, 1)
 
 
         # Exception Areas
         # Exception Areas
         self.except_cb = FCCheckBox(label=_('Except'))
         self.except_cb = FCCheckBox(label=_('Except'))
@@ -269,7 +269,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
                                     "by checking this, the area of the object below\n"
                                     "by checking this, the area of the object below\n"
                                     "will be subtracted from the isolation geometry."))
                                     "will be subtracted from the isolation geometry."))
         self.except_cb.setObjectName("i_except")
         self.except_cb.setObjectName("i_except")
-        grid0.addWidget(self.except_cb, 19, 0, 1, 2)
+        grid0.addWidget(self.except_cb, 17, 2)
 
 
         # Isolation Scope
         # Isolation Scope
         self.select_label = QtWidgets.QLabel('%s:' % _("Selection"))
         self.select_label = QtWidgets.QLabel('%s:' % _("Selection"))
@@ -286,7 +286,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.select_combo.setObjectName("i_selection")
         self.select_combo.setObjectName("i_selection")
 
 
         grid0.addWidget(self.select_label, 20, 0)
         grid0.addWidget(self.select_label, 20, 0)
-        grid0.addWidget(self.select_combo, 20, 1)
+        grid0.addWidget(self.select_combo, 20, 1, 1, 2)
 
 
         # Area Shape
         # Area Shape
         self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
         self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
@@ -298,22 +298,22 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
                                           {'label': _("Polygon"), 'value': 'polygon'}])
                                           {'label': _("Polygon"), 'value': 'polygon'}])
 
 
         grid0.addWidget(self.area_shape_label, 21, 0)
         grid0.addWidget(self.area_shape_label, 21, 0)
-        grid0.addWidget(self.area_shape_radio, 21, 1)
+        grid0.addWidget(self.area_shape_radio, 21, 1, 1, 2)
 
 
         separator_line = QtWidgets.QFrame()
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 22, 0, 1, 2)
+        grid0.addWidget(separator_line, 22, 0, 1, 3)
 
 
         # ## Plotting type
         # ## Plotting type
-        self.ncc_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
-                                            {"label": _("Progressive"), "value": "progressive"}])
-        plotting_label = QtWidgets.QLabel('%s:' % _("ISO Plotting"))
+        self.plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
+                                        {"label": _("Progressive"), "value": "progressive"}])
+        plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
         plotting_label.setToolTip(
         plotting_label.setToolTip(
             _("- 'Normal' -  normal plotting, done at the end of the job\n"
             _("- 'Normal' -  normal plotting, done at the end of the job\n"
               "- 'Progressive' - each shape is plotted after it is generated")
               "- 'Progressive' - each shape is plotted after it is generated")
         )
         )
-        grid0.addWidget(plotting_label, 21, 0)
-        grid0.addWidget(self.ncc_plotting_radio, 21, 1)
+        grid0.addWidget(plotting_label, 23, 0)
+        grid0.addWidget(self.plotting_radio, 23, 1, 1, 2)
 
 
         self.layout.addStretch()
         self.layout.addStretch()

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

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

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

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

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

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

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

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

+ 33 - 9
AppObjects/FlatCAMExcellon.py

@@ -1180,13 +1180,25 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
 
     def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
     def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
         """
         """
+        Will generate an Geometry Object allowing to cut a drill hole instead of drilling it.
+
         Note: This method is a good template for generic operations as
         Note: This method is a good template for generic operations as
         it takes it's options from parameters or otherwise from the
         it takes it's options from parameters or otherwise from the
         object's options and returns a (success, msg) tuple as feedback
         object's options and returns a (success, msg) tuple as feedback
         for shell operations.
         for shell operations.
 
 
-        :return:    Success/failure condition tuple (bool, str).
-        :rtype:     tuple
+        :param tools:       A list of tools where the drills are to be milled or a string: "all"
+        :type tools:
+        :param outname:     the name of the resulting Geometry object
+        :type outname:      str
+        :param tooldia:     the tool diameter to be used in creation of the milling path (Geometry Object)
+        :type tooldia:      float
+        :param plot:        if to plot the resulting object
+        :type plot:         bool
+        :param use_thread:  if to use threading for creation of the Geometry object
+        :type use_thread:   bool
+        :return:            Success/failure condition tuple (bool, str).
+        :rtype:             tuple
         """
         """
 
 
         # Get the tools from the list. These are keys
         # Get the tools from the list. These are keys
@@ -1250,7 +1262,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options["cnctooldia"] = str(tooldia)
             geo_obj.options["cnctooldia"] = str(tooldia)
-
+            geo_obj.options["multidepth"] = self.options["multidepth"]
             geo_obj.solid_geometry = []
             geo_obj.solid_geometry = []
 
 
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
@@ -1280,15 +1292,27 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
 
         return True, ""
         return True, ""
 
 
-    def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=True, use_thread=False):
+    def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
         """
         """
+        Will generate an Geometry Object allowing to cut/mill a slot hole.
+
         Note: This method is a good template for generic operations as
         Note: This method is a good template for generic operations as
         it takes it's options from parameters or otherwise from the
         it takes it's options from parameters or otherwise from the
         object's options and returns a (success, msg) tuple as feedback
         object's options and returns a (success, msg) tuple as feedback
         for shell operations.
         for shell operations.
 
 
-        :return: Success/failure condition tuple (bool, str).
-        :rtype: tuple
+        :param tools:       A list of tools where the drills are to be milled or a string: "all"
+        :type tools:
+        :param outname:     the name of the resulting Geometry object
+        :type outname:      str
+        :param tooldia:     the tool diameter to be used in creation of the milling path (Geometry Object)
+        :type tooldia:      float
+        :param plot:        if to plot the resulting object
+        :type plot:         bool
+        :param use_thread:  if to use threading for creation of the Geometry object
+        :type use_thread:   bool
+        :return:            Success/failure condition tuple (bool, str).
+        :rtype:             tuple
         """
         """
 
 
         # Get the tools from the list. These are keys
         # Get the tools from the list. These are keys
@@ -1341,7 +1365,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options['type'] = 'Excellon Geometry'
             geo_obj.options["cnctooldia"] = str(tooldia)
             geo_obj.options["cnctooldia"] = str(tooldia)
-
+            geo_obj.options["multidepth"] = self.options["multidepth"]
             geo_obj.solid_geometry = []
             geo_obj.solid_geometry = []
 
 
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
@@ -1388,13 +1412,13 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.app.defaults.report_usage("excellon_on_create_milling_drills button")
         self.app.defaults.report_usage("excellon_on_create_milling_drills button")
         self.read_form()
         self.read_form()
 
 
-        self.generate_milling_drills(use_thread=False)
+        self.generate_milling_drills(use_thread=False, plot=True)
 
 
     def on_generate_milling_slots_button_click(self, *args):
     def on_generate_milling_slots_button_click(self, *args):
         self.app.defaults.report_usage("excellon_on_create_milling_slots_button")
         self.app.defaults.report_usage("excellon_on_create_milling_slots_button")
         self.read_form()
         self.read_form()
 
 
-        self.generate_milling_slots(use_thread=False)
+        self.generate_milling_slots(use_thread=False, plot=True)
 
 
     def on_pp_changed(self):
     def on_pp_changed(self):
         current_pp = self.ui.pp_excellon_name_cb.get_value()
         current_pp = self.ui.pp_excellon_name_cb.get_value()

+ 3 - 688
AppObjects/FlatCAMGerber.py

@@ -115,23 +115,12 @@ class GerberObject(FlatCAMObj, Gerber):
             "plot": True,
             "plot": True,
             "multicolored": False,
             "multicolored": False,
             "solid": False,
             "solid": False,
-            "tool_type": 'circular',
-            "vtipdia": 0.1,
-            "vtipangle": 30,
-            "vcutz": -0.05,
-            "isotooldia": 0.016,
-            "isopasses": 1,
-            "isooverlap": 15,
-            "milling_type": "cl",
-            "combine_passes": True,
             "noncoppermargin": 0.0,
             "noncoppermargin": 0.0,
             "noncopperrounded": False,
             "noncopperrounded": False,
             "bboxmargin": 0.0,
             "bboxmargin": 0.0,
             "bboxrounded": False,
             "bboxrounded": False,
             "aperture_display": False,
             "aperture_display": False,
             "follow": False,
             "follow": False,
-            "iso_scope": 'all',
-            "iso_type": 'full'
         })
         })
 
 
         # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
         # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
@@ -197,33 +186,22 @@ class GerberObject(FlatCAMObj, Gerber):
             "plot": self.ui.plot_cb,
             "plot": self.ui.plot_cb,
             "multicolored": self.ui.multicolored_cb,
             "multicolored": self.ui.multicolored_cb,
             "solid": self.ui.solid_cb,
             "solid": self.ui.solid_cb,
-            "tool_type": self.ui.tool_type_radio,
-            "vtipdia": self.ui.tipdia_spinner,
-            "vtipangle": self.ui.tipangle_spinner,
-            "vcutz": self.ui.cutz_spinner,
-            "isotooldia": self.ui.iso_tool_dia_entry,
-            "isopasses": self.ui.iso_width_entry,
-            "isooverlap": self.ui.iso_overlap_entry,
-            "milling_type": self.ui.milling_type_radio,
-            "combine_passes": self.ui.combine_passes_cb,
             "noncoppermargin": self.ui.noncopper_margin_entry,
             "noncoppermargin": self.ui.noncopper_margin_entry,
             "noncopperrounded": self.ui.noncopper_rounded_cb,
             "noncopperrounded": self.ui.noncopper_rounded_cb,
             "bboxmargin": self.ui.bbmargin_entry,
             "bboxmargin": self.ui.bbmargin_entry,
             "bboxrounded": self.ui.bbrounded_cb,
             "bboxrounded": self.ui.bbrounded_cb,
             "aperture_display": self.ui.aperture_table_visibility_cb,
             "aperture_display": self.ui.aperture_table_visibility_cb,
-            "follow": self.ui.follow_cb,
-            "iso_scope": self.ui.iso_scope_radio,
-            "iso_type": self.ui.iso_type_radio
+            "follow": self.ui.follow_cb
         })
         })
 
 
         # Fill form fields only on object create
         # Fill form fields only on object create
         self.to_form()
         self.to_form()
 
 
         assert isinstance(self.ui, GerberObjectUI)
         assert isinstance(self.ui, GerberObjectUI)
+
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
         self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
         self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
         self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
-        self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
 
 
         # Tools
         # Tools
         self.ui.iso_button.clicked.connect(self.app.isolation_tool.run)
         self.ui.iso_button.clicked.connect(self.app.isolation_tool.run)
@@ -235,54 +213,17 @@ class GerberObject(FlatCAMObj, Gerber):
         self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
         self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
         self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
         self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
 
 
-        # set the model for the Area Exception comboboxes
-        self.ui.obj_combo.setModel(self.app.collection)
-        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.ui.obj_combo.is_last = True
-        self.ui.obj_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Geometry"): "Geometry"
-        }[self.ui.type_obj_combo.get_value()]
-        self.on_type_obj_index_changed()
-
-        self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
-
-        self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type_change)
-        # establish visibility for the GUI elements found in the slot function
-        self.ui.tool_type_radio.activated_custom.emit(self.options['tool_type'])
-
         # Show/Hide Advanced Options
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
         if self.app.defaults["global_app_level"] == 'b':
             self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
             self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
-            self.options['tool_type'] = 'circular'
-
-            self.ui.tool_type_label.hide()
-            self.ui.tool_type_radio.hide()
-
-            # override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1')
-            self.ui.tool_type_radio.set_value('circular')
-
-            self.ui.tipdialabel.hide()
-            self.ui.tipdia_spinner.hide()
-            self.ui.tipanglelabel.hide()
-            self.ui.tipangle_spinner.hide()
-            self.ui.cutzlabel.hide()
-            self.ui.cutz_spinner.hide()
 
 
             self.ui.apertures_table_label.hide()
             self.ui.apertures_table_label.hide()
             self.ui.aperture_table_visibility_cb.hide()
             self.ui.aperture_table_visibility_cb.hide()
-            self.ui.milling_type_label.hide()
-            self.ui.milling_type_radio.hide()
-            self.ui.iso_type_label.hide()
-            self.ui.iso_type_radio.hide()
 
 
             self.ui.follow_cb.hide()
             self.ui.follow_cb.hide()
-            self.ui.except_cb.setChecked(False)
-            self.ui.except_cb.hide()
+
         else:
         else:
             self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
             self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
-            self.ui.tipdia_spinner.valueChanged.connect(self.on_calculate_tooldia)
-            self.ui.tipangle_spinner.valueChanged.connect(self.on_calculate_tooldia)
-            self.ui.cutz_spinner.valueChanged.connect(self.on_calculate_tooldia)
 
 
         if self.app.defaults["gerber_buffering"] == 'no':
         if self.app.defaults["gerber_buffering"] == 'no':
             self.ui.create_buffer_button.show()
             self.ui.create_buffer_button.show()
@@ -300,58 +241,6 @@ class GerberObject(FlatCAMObj, Gerber):
         self.build_ui()
         self.build_ui()
         self.units_found = self.app.defaults['units']
         self.units_found = self.app.defaults['units']
 
 
-    def on_calculate_tooldia(self):
-        try:
-            tdia = float(self.ui.tipdia_spinner.get_value())
-        except Exception:
-            return
-        try:
-            dang = float(self.ui.tipangle_spinner.get_value())
-        except Exception:
-            return
-        try:
-            cutz = float(self.ui.cutz_spinner.get_value())
-        except Exception:
-            return
-
-        cutz *= -1
-        if cutz < 0:
-            cutz *= -1
-
-        half_tip_angle = dang / 2
-
-        tool_diameter = tdia + (2 * cutz * math.tan(math.radians(half_tip_angle)))
-        self.ui.iso_tool_dia_entry.set_value(tool_diameter)
-
-    def on_type_obj_index_changed(self):
-        val = self.ui.type_obj_combo.get_value()
-        obj_type = {"Gerber": 0, "Geometry": 2}[val]
-        self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.ui.obj_combo.setCurrentIndex(0)
-        self.ui.obj_combo.obj_type = {_("Gerber"): "Gerber", _("Geometry"): "Geometry"}[val]
-
-    def on_tool_type_change(self, state):
-        if state == 'circular':
-            self.ui.tipdialabel.hide()
-            self.ui.tipdia_spinner.hide()
-            self.ui.tipanglelabel.hide()
-            self.ui.tipangle_spinner.hide()
-            self.ui.cutzlabel.hide()
-            self.ui.cutz_spinner.hide()
-            self.ui.iso_tool_dia_entry.setDisabled(False)
-            # update the value in the self.iso_tool_dia_entry once this is selected
-            self.ui.iso_tool_dia_entry.set_value(self.options['isotooldia'])
-        else:
-            self.ui.tipdialabel.show()
-            self.ui.tipdia_spinner.show()
-            self.ui.tipanglelabel.show()
-            self.ui.tipangle_spinner.show()
-            self.ui.cutzlabel.show()
-            self.ui.cutz_spinner.show()
-            self.ui.iso_tool_dia_entry.setDisabled(True)
-            # update the value in the self.iso_tool_dia_entry once this is selected
-            self.on_calculate_tooldia()
-
     def build_ui(self):
     def build_ui(self):
         FlatCAMObj.build_ui(self)
         FlatCAMObj.build_ui(self)
 
 
@@ -562,521 +451,6 @@ class GerberObject(FlatCAMObj, Gerber):
 
 
         self.app.app_obj.new_object("geometry", name, geo_init)
         self.app.app_obj.new_object("geometry", name, geo_init)
 
 
-    def on_iso_button_click(self, *args):
-
-        obj = self.app.collection.get_active()
-
-        self.iso_type = 2
-        if self.ui.iso_type_radio.get_value() == 'ext':
-            self.iso_type = 0
-        if self.ui.iso_type_radio.get_value() == 'int':
-            self.iso_type = 1
-
-        def worker_task(iso_obj, app_obj):
-            with self.app.proc_container.new(_("Isolating...")):
-                if self.ui.follow_cb.get_value() is True:
-                    iso_obj.follow_geo()
-                    # in the end toggle the visibility of the origin object so we can see the generated Geometry
-                    iso_obj.ui.plot_cb.toggle()
-                else:
-                    app_obj.defaults.report_usage("gerber_on_iso_button")
-                    self.read_form()
-
-                    iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single'
-                    self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
-
-        self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
-
-    def follow_geo(self, outname=None):
-        """
-        Creates a geometry object "following" the gerber paths.
-
-        :return: None
-        """
-
-        # default_name = self.options["name"] + "_follow"
-        # follow_name = outname or default_name
-
-        if outname is None:
-            follow_name = self.options["name"] + "_follow"
-        else:
-            follow_name = outname
-
-        def follow_init(follow_obj, app):
-            # Propagate options
-            follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
-            follow_obj.solid_geometry = self.follow_geometry
-
-        # TODO: Do something if this is None. Offer changing name?
-        try:
-            self.app.app_obj.new_object("geometry", follow_name, follow_init)
-        except Exception as e:
-            return "Operation failed: %s" % str(e)
-
-    def isolate_handler(self, iso_type, iso_scope):
-
-        if iso_scope == 'all':
-            self.isolate(iso_type=iso_type)
-        else:
-            # disengage the grid snapping since it may be hard to click on polygons with grid snapping on
-            if self.app.ui.grid_snap_btn.isChecked():
-                self.grid_status_memory = True
-                self.app.ui.grid_snap_btn.trigger()
-            else:
-                self.grid_status_memory = False
-
-            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
-
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
-
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
-
-    def on_mouse_click_release(self, event):
-        if self.app.is_legacy is False:
-            event_pos = event.pos
-            right_button = 2
-            self.app.event_is_dragging = self.app.event_is_dragging
-        else:
-            event_pos = (event.xdata, event.ydata)
-            right_button = 3
-            self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
-
-        try:
-            x = float(event_pos[0])
-            y = float(event_pos[1])
-        except TypeError:
-            return
-
-        event_pos = (x, y)
-        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
-        if self.app.grid_status():
-            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
-        else:
-            curr_pos = (curr_pos[0], curr_pos[1])
-
-        if event.button == 1:
-            clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
-
-            if self.app.selection_type is not None:
-                self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
-                self.app.selection_type = None
-            elif clicked_poly:
-                if clicked_poly not in self.poly_dict.values():
-                    shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
-                                                        color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                        face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                        visible=True)
-                    self.poly_dict[shape_id] = clicked_poly
-                    self.app.inform.emit(
-                        '%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)),
-                                        _("Click to add next polygon or right click to start isolation."))
-                    )
-                else:
-                    try:
-                        for k, v in list(self.poly_dict.items()):
-                            if v == clicked_poly:
-                                self.app.tool_shapes.remove(k)
-                                self.poly_dict.pop(k)
-                                break
-                    except TypeError:
-                        return
-                    self.app.inform.emit(
-                        '%s. %s' % (_("Removed polygon"),
-                                    _("Click to add/remove next polygon or right click to start isolation."))
-                    )
-
-                self.app.tool_shapes.redraw()
-            else:
-                self.app.inform.emit(_("No polygon detected under click position."))
-        elif event.button == right_button and self.app.event_is_dragging is False:
-            # restore the Grid snapping if it was active before
-            if self.grid_status_memory is True:
-                self.app.ui.grid_snap_btn.trigger()
-
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.mr)
-
-            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                                  self.app.on_mouse_click_release_over_plot)
-
-            self.app.tool_shapes.clear(update=True)
-
-            if self.poly_dict:
-                poly_list = deepcopy(list(self.poly_dict.values()))
-                self.isolate(iso_type=self.iso_type, geometry=poly_list)
-                self.poly_dict.clear()
-            else:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
-
-    def selection_area_handler(self, start_pos, end_pos, sel_type):
-        """
-        :param start_pos: mouse position when the selection LMB click was done
-        :param end_pos: mouse position when the left mouse button is released
-        :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
-        :return:
-        """
-        poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
-
-        # delete previous selection shape
-        self.app.delete_selection_shape()
-
-        added_poly_count = 0
-        try:
-            for geo in self.solid_geometry:
-                if geo not in self.poly_dict.values():
-                    if sel_type is True:
-                        if geo.within(poly_selection):
-                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                                shape=geo,
-                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                                face_color=self.app.defaults[
-                                                                               'global_sel_draw_color'] + 'AF',
-                                                                visible=True)
-                            self.poly_dict[shape_id] = geo
-                            added_poly_count += 1
-                    else:
-                        if poly_selection.intersects(geo):
-                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                                shape=geo,
-                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                                face_color=self.app.defaults[
-                                                                               'global_sel_draw_color'] + 'AF',
-                                                                visible=True)
-                            self.poly_dict[shape_id] = geo
-                            added_poly_count += 1
-        except TypeError:
-            if self.solid_geometry not in self.poly_dict.values():
-                if sel_type is True:
-                    if self.solid_geometry.within(poly_selection):
-                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                            shape=self.solid_geometry,
-                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                            face_color=self.app.defaults[
-                                                                           'global_sel_draw_color'] + 'AF',
-                                                            visible=True)
-                        self.poly_dict[shape_id] = self.solid_geometry
-                        added_poly_count += 1
-                else:
-                    if poly_selection.intersects(self.solid_geometry):
-                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
-                                                            shape=self.solid_geometry,
-                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
-                                                            face_color=self.app.defaults[
-                                                                           'global_sel_draw_color'] + 'AF',
-                                                            visible=True)
-                        self.poly_dict[shape_id] = self.solid_geometry
-                        added_poly_count += 1
-
-        if added_poly_count > 0:
-            self.app.tool_shapes.redraw()
-            self.app.inform.emit(
-                '%s: %d. %s' % (_("Added polygon"),
-                                int(added_poly_count),
-                                _("Click to add next polygon or right click to start isolation."))
-            )
-        else:
-            self.app.inform.emit(_("No polygon in selection."))
-
-    def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
-                milling_type=None, follow=None, plot=True):
-        """
-        Creates an isolation routing geometry object in the project.
-
-        :param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
-        :param geometry: specific geometry to isolate
-        :param dia: Tool diameter
-        :param passes: Number of tool widths to cut
-        :param overlap: Overlap between passes in fraction of tool diameter
-        :param outname: Base name of the output object
-        :param combine: Boolean: if to combine passes in one resulting object in case of multiple passes
-        :param milling_type: type of milling: conventional or climbing
-        :param follow: Boolean: if to generate a 'follow' geometry
-        :param plot: Boolean: if to plot the resulting geometry object
-        :return: None
-        """
-
-        if geometry is None:
-            work_geo = self.follow_geometry if follow is True else self.solid_geometry
-        else:
-            work_geo = geometry
-
-        if dia is None:
-            dia = float(self.options["isotooldia"])
-
-        if passes is None:
-            passes = int(self.options["isopasses"])
-
-        if overlap is None:
-            overlap = float(self.options["isooverlap"])
-
-        overlap /= 100.0
-
-        combine = self.options["combine_passes"] if combine is None else bool(combine)
-
-        if milling_type is None:
-            milling_type = self.options["milling_type"]
-
-        if iso_type is None:
-            iso_t = 2
-        else:
-            iso_t = iso_type
-
-        base_name = self.options["name"]
-
-        if combine:
-            if outname is None:
-                if self.iso_type == 0:
-                    iso_name = base_name + "_ext_iso"
-                elif self.iso_type == 1:
-                    iso_name = base_name + "_int_iso"
-                else:
-                    iso_name = base_name + "_iso"
-            else:
-                iso_name = outname
-
-            def iso_init(geo_obj, app_obj):
-                # Propagate options
-                geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
-                geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
-
-                geo_obj.solid_geometry = []
-
-                # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
-                if self.ui.tool_type_radio.get_value() == 'v':
-                    new_cutz = self.ui.cutz_spinner.get_value()
-                    new_vtipdia = self.ui.tipdia_spinner.get_value()
-                    new_vtipangle = self.ui.tipangle_spinner.get_value()
-                    tool_type = 'V'
-                else:
-                    new_cutz = self.app.defaults['geometry_cutz']
-                    new_vtipdia = self.app.defaults['geometry_vtipdia']
-                    new_vtipangle = self.app.defaults['geometry_vtipangle']
-                    tool_type = 'C1'
-
-                # store here the default data for Geometry Data
-                default_data = {}
-                default_data.update({
-                    "name": iso_name,
-                    "plot": self.app.defaults['geometry_plot'],
-                    "cutz": new_cutz,
-                    "vtipdia": new_vtipdia,
-                    "vtipangle": new_vtipangle,
-                    "travelz": self.app.defaults['geometry_travelz'],
-                    "feedrate": self.app.defaults['geometry_feedrate'],
-                    "feedrate_z": self.app.defaults['geometry_feedrate_z'],
-                    "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
-                    "dwell": self.app.defaults['geometry_dwell'],
-                    "dwelltime": self.app.defaults['geometry_dwelltime'],
-                    "multidepth": self.app.defaults['geometry_multidepth'],
-                    "ppname_g": self.app.defaults['geometry_ppname_g'],
-                    "depthperpass": self.app.defaults['geometry_depthperpass'],
-                    "extracut": self.app.defaults['geometry_extracut'],
-                    "extracut_length": self.app.defaults['geometry_extracut_length'],
-                    "toolchange": self.app.defaults['geometry_toolchange'],
-                    "toolchangez": self.app.defaults['geometry_toolchangez'],
-                    "endz": self.app.defaults['geometry_endz'],
-                    "spindlespeed": self.app.defaults['geometry_spindlespeed'],
-                    "toolchangexy": self.app.defaults['geometry_toolchangexy'],
-                    "startz": self.app.defaults['geometry_startz']
-                })
-
-                geo_obj.tools = {}
-                geo_obj.tools['1'] = {}
-                geo_obj.tools.update({
-                    '1': {
-                        'tooldia': float(self.options["isotooldia"]),
-                        'offset': 'Path',
-                        'offset_value': 0.0,
-                        'type': _('Rough'),
-                        'tool_type': tool_type,
-                        'data': default_data,
-                        'solid_geometry': geo_obj.solid_geometry
-                    }
-                })
-
-                for nr_pass in range(passes):
-                    iso_offset = dia * ((2 * nr_pass + 1) / 2.0000001) - (nr_pass * overlap * dia)
-
-                    # if milling type is climb then the move is counter-clockwise around features
-                    mill_dir = 1 if milling_type == 'cl' else 0
-                    geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
-                                                  follow=follow, nr_passes=nr_pass)
-
-                    if geom == 'fail':
-                        app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
-                        return 'fail'
-                    geo_obj.solid_geometry.append(geom)
-
-                    # update the geometry in the tools
-                    geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry
-
-                # detect if solid_geometry is empty and this require list flattening which is "heavy"
-                # or just looking in the lists (they are one level depth) and if any is not empty
-                # proceed with object creation, if there are empty and the number of them is the length
-                # of the list then we have an empty solid_geometry which should raise a Custom Exception
-                empty_cnt = 0
-                if not isinstance(geo_obj.solid_geometry, list) and \
-                        not isinstance(geo_obj.solid_geometry, MultiPolygon):
-                    geo_obj.solid_geometry = [geo_obj.solid_geometry]
-
-                for g in geo_obj.solid_geometry:
-                    if g:
-                        break
-                    else:
-                        empty_cnt += 1
-
-                if empty_cnt == len(geo_obj.solid_geometry):
-                    raise ValidationError("Empty Geometry", None)
-                else:
-                    app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
-
-                # even if combine is checked, one pass is still single-geo
-                geo_obj.multigeo = True if passes > 1 else False
-
-                # ############################################################
-                # ########## AREA SUBTRACTION ################################
-                # ############################################################
-                if self.ui.except_cb.get_value():
-                    self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                    geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
-
-            # TODO: Do something if this is None. Offer changing name?
-            self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
-        else:
-            for i in range(passes):
-                offset = dia * ((2 * i + 1) / 2.0000001) - (i * overlap * dia)
-                if passes > 1:
-                    if outname is None:
-                        if self.iso_type == 0:
-                            iso_name = base_name + "_ext_iso" + str(i + 1)
-                        elif self.iso_type == 1:
-                            iso_name = base_name + "_int_iso" + str(i + 1)
-                        else:
-                            iso_name = base_name + "_iso" + str(i + 1)
-                    else:
-                        iso_name = outname
-                else:
-                    if outname is None:
-                        if self.iso_type == 0:
-                            iso_name = base_name + "_ext_iso"
-                        elif self.iso_type == 1:
-                            iso_name = base_name + "_int_iso"
-                        else:
-                            iso_name = base_name + "_iso"
-                    else:
-                        iso_name = outname
-
-                def iso_init(geo_obj, fc_obj):
-                    # Propagate options
-                    geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
-                    if self.ui.tool_type_radio.get_value() == 'v':
-                        geo_obj.tool_type = 'V'
-                    else:
-                        geo_obj.tool_type = 'C1'
-
-                    # if milling type is climb then the move is counter-clockwise around features
-                    mill_dir = 1 if milling_type == 'cl' else 0
-                    geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
-                                                  follow=follow,
-                                                  nr_passes=i)
-
-                    if geom == 'fail':
-                        fc_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
-                        return 'fail'
-
-                    geo_obj.solid_geometry = geom
-
-                    # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
-                    # even if the resulting geometry is not multigeo we add the tools dict which will hold the data
-                    # required to be transfered to the Geometry object
-                    if self.ui.tool_type_radio.get_value() == 'v':
-                        new_cutz = self.ui.cutz_spinner.get_value()
-                        new_vtipdia = self.ui.tipdia_spinner.get_value()
-                        new_vtipangle = self.ui.tipangle_spinner.get_value()
-                        tool_type = 'V'
-                    else:
-                        new_cutz = self.app.defaults['geometry_cutz']
-                        new_vtipdia = self.app.defaults['geometry_vtipdia']
-                        new_vtipangle = self.app.defaults['geometry_vtipangle']
-                        tool_type = 'C1'
-
-                    # store here the default data for Geometry Data
-                    default_data = {}
-                    default_data.update({
-                        "name": iso_name,
-                        "plot": self.app.defaults['geometry_plot'],
-                        "cutz": new_cutz,
-                        "vtipdia": new_vtipdia,
-                        "vtipangle": new_vtipangle,
-                        "travelz": self.app.defaults['geometry_travelz'],
-                        "feedrate": self.app.defaults['geometry_feedrate'],
-                        "feedrate_z": self.app.defaults['geometry_feedrate_z'],
-                        "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
-                        "dwell": self.app.defaults['geometry_dwell'],
-                        "dwelltime": self.app.defaults['geometry_dwelltime'],
-                        "multidepth": self.app.defaults['geometry_multidepth'],
-                        "ppname_g": self.app.defaults['geometry_ppname_g'],
-                        "depthperpass": self.app.defaults['geometry_depthperpass'],
-                        "extracut": self.app.defaults['geometry_extracut'],
-                        "extracut_length": self.app.defaults['geometry_extracut_length'],
-                        "toolchange": self.app.defaults['geometry_toolchange'],
-                        "toolchangez": self.app.defaults['geometry_toolchangez'],
-                        "endz": self.app.defaults['geometry_endz'],
-                        "spindlespeed": self.app.defaults['geometry_spindlespeed'],
-                        "toolchangexy": self.app.defaults['geometry_toolchangexy'],
-                        "startz": self.app.defaults['geometry_startz']
-                    })
-
-                    geo_obj.tools = {}
-                    geo_obj.tools['1'] = {}
-                    geo_obj.tools.update({
-                        '1': {
-                            'tooldia': float(self.options["isotooldia"]),
-                            'offset': 'Path',
-                            'offset_value': 0.0,
-                            'type': _('Rough'),
-                            'tool_type': tool_type,
-                            'data': default_data,
-                            'solid_geometry': geo_obj.solid_geometry
-                        }
-                    })
-
-                    # detect if solid_geometry is empty and this require list flattening which is "heavy"
-                    # or just looking in the lists (they are one level depth) and if any is not empty
-                    # proceed with object creation, if there are empty and the number of them is the length
-                    # of the list then we have an empty solid_geometry which should raise a Custom Exception
-                    empty_cnt = 0
-                    if not isinstance(geo_obj.solid_geometry, list):
-                        geo_obj.solid_geometry = [geo_obj.solid_geometry]
-
-                    for g in geo_obj.solid_geometry:
-                        if g:
-                            break
-                        else:
-                            empty_cnt += 1
-
-                    if empty_cnt == len(geo_obj.solid_geometry):
-                        raise ValidationError("Empty Geometry", None)
-                    else:
-                        fc_obj.inform.emit('[success] %s: %s' %
-                                            (_("Isolation geometry created"), geo_obj.options["name"]))
-                    geo_obj.multigeo = False
-
-                    # ############################################################
-                    # ########## AREA SUBTRACTION ################################
-                    # ############################################################
-                    if self.ui.except_cb.get_value():
-                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
-                        geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
-
-                # TODO: Do something if this is None. Offer changing name?
-                self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
-
     def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
     def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
         # isolation_geometry produces an envelope that is going on the left of the geometry
         # isolation_geometry produces an envelope that is going on the left of the geometry
         # (the copper features). To leave the least amount of burrs on the features
         # (the copper features). To leave the least amount of burrs on the features
@@ -1117,65 +491,6 @@ class GerberObject(FlatCAMObj, Gerber):
                 return 'fail'
                 return 'fail'
         return geom
         return geom
 
 
-    def area_subtraction(self, geo, subtractor_geo=None):
-        """
-        Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo
-
-        :param geo: target geometry from which to subtract
-        :param subtractor_geo: geometry that acts as subtractor
-        :return:
-        """
-        new_geometry = []
-        target_geo = geo
-
-        if subtractor_geo:
-            sub_union = cascaded_union(subtractor_geo)
-        else:
-            name = self.ui.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):
-                    new_geo = geo_elem.difference(sub_union)
-                    if new_geo:
-                        if not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-                elif isinstance(geo_elem, MultiLineString):
-                    for line_elem in geo_elem:
-                        new_geo = line_elem.difference(sub_union)
-                        if new_geo and not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-        except TypeError:
-            if isinstance(target_geo, Polygon):
-                for ring in self.poly2rings(target_geo):
-                    new_geo = ring.difference(sub_union)
-                    if new_geo:
-                        if not new_geo.is_empty:
-                            new_geometry.append(new_geo)
-            elif isinstance(target_geo, LineString):
-                new_geo = target_geo.difference(sub_union)
-                if new_geo and not new_geo.is_empty:
-                    new_geometry.append(new_geo)
-            elif isinstance(target_geo, MultiLineString):
-                for line_elem in target_geo:
-                    new_geo = line_elem.difference(sub_union)
-                    if new_geo and not new_geo.is_empty:
-                        new_geometry.append(new_geo)
-        return new_geometry
-
     def on_plot_cb_click(self, *args):
     def on_plot_cb_click(self, *args):
         if self.muted_ui:
         if self.muted_ui:
             return
             return

+ 3 - 3
AppTools/ToolEtchCompensation.py

@@ -95,7 +95,7 @@ class ToolEtchCompensation(AppTool):
 
 
         hlay_1 = QtWidgets.QHBoxLayout()
         hlay_1 = QtWidgets.QHBoxLayout()
 
 
-        self.oz_entry = NumericalEvalEntry()
+        self.oz_entry = NumericalEvalEntry(border_color='#0069A9')
         self.oz_entry.setPlaceholderText(_("Oz value"))
         self.oz_entry.setPlaceholderText(_("Oz value"))
         self.oz_to_um_entry = FCEntry()
         self.oz_to_um_entry = FCEntry()
         self.oz_to_um_entry.setPlaceholderText(_("Microns value"))
         self.oz_to_um_entry.setPlaceholderText(_("Microns value"))
@@ -116,7 +116,7 @@ class ToolEtchCompensation(AppTool):
 
 
         hlay_2 = QtWidgets.QHBoxLayout()
         hlay_2 = QtWidgets.QHBoxLayout()
 
 
-        self.mils_entry = NumericalEvalEntry()
+        self.mils_entry = NumericalEvalEntry(border_color='#0069A9')
         self.mils_entry.setPlaceholderText(_("Mils value"))
         self.mils_entry.setPlaceholderText(_("Mils value"))
         self.mils_to_um_entry = FCEntry()
         self.mils_to_um_entry = FCEntry()
         self.mils_to_um_entry.setPlaceholderText(_("Microns value"))
         self.mils_to_um_entry.setPlaceholderText(_("Microns value"))
@@ -180,7 +180,7 @@ class ToolEtchCompensation(AppTool):
             _("The ratio between depth etch and lateral etch .\n"
             _("The ratio between depth etch and lateral etch .\n"
               "Accepts real numbers and formulas using the operators: /,*,+,-,%")
               "Accepts real numbers and formulas using the operators: /,*,+,-,%")
         )
         )
-        self.factor_entry = NumericalEvalEntry()
+        self.factor_entry = NumericalEvalEntry(border_color='#0069A9')
         self.factor_entry.setPlaceholderText(_("Real number or formula"))
         self.factor_entry.setPlaceholderText(_("Real number or formula"))
         self.factor_entry.setObjectName(_("Etch_factor"))
         self.factor_entry.setObjectName(_("Etch_factor"))
 
 

+ 626 - 101
AppTools/ToolIsolation.py

@@ -213,7 +213,7 @@ class ToolIsolation(AppTool, Gerber):
               "- 'V-shape'\n"
               "- 'V-shape'\n"
               "- Circular")
               "- Circular")
         )
         )
-        self.tool_type_radio.setObjectName(_("Tool Type"))
+        self.tool_type_radio.setObjectName("i_tool_type")
 
 
         self.grid3.addWidget(self.tool_type_label, 2, 0)
         self.grid3.addWidget(self.tool_type_label, 2, 0)
         self.grid3.addWidget(self.tool_type_radio, 2, 1)
         self.grid3.addWidget(self.tool_type_radio, 2, 1)
@@ -226,7 +226,7 @@ class ToolIsolation(AppTool, Gerber):
         self.tipdia_entry.set_precision(self.decimals)
         self.tipdia_entry.set_precision(self.decimals)
         self.tipdia_entry.set_range(0.0000, 9999.9999)
         self.tipdia_entry.set_range(0.0000, 9999.9999)
         self.tipdia_entry.setSingleStep(0.1)
         self.tipdia_entry.setSingleStep(0.1)
-        self.tipdia_entry.setObjectName(_("V-Tip Dia"))
+        self.tipdia_entry.setObjectName("i_vtipdia")
 
 
         self.grid3.addWidget(self.tipdialabel, 3, 0)
         self.grid3.addWidget(self.tipdialabel, 3, 0)
         self.grid3.addWidget(self.tipdia_entry, 3, 1)
         self.grid3.addWidget(self.tipdia_entry, 3, 1)
@@ -240,7 +240,7 @@ class ToolIsolation(AppTool, Gerber):
         self.tipangle_entry.set_precision(self.decimals)
         self.tipangle_entry.set_precision(self.decimals)
         self.tipangle_entry.set_range(0.0000, 180.0000)
         self.tipangle_entry.set_range(0.0000, 180.0000)
         self.tipangle_entry.setSingleStep(5)
         self.tipangle_entry.setSingleStep(5)
-        self.tipangle_entry.setObjectName(_("V-Tip Angle"))
+        self.tipangle_entry.setObjectName("i_vtipangle")
 
 
         self.grid3.addWidget(self.tipanglelabel, 4, 0)
         self.grid3.addWidget(self.tipanglelabel, 4, 0)
         self.grid3.addWidget(self.tipangle_entry, 4, 1)
         self.grid3.addWidget(self.tipangle_entry, 4, 1)
@@ -254,7 +254,7 @@ class ToolIsolation(AppTool, Gerber):
         self.cutz_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.cutz_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.cutz_entry.set_precision(self.decimals)
         self.cutz_entry.set_precision(self.decimals)
         self.cutz_entry.set_range(-99999.9999, 0.0000)
         self.cutz_entry.set_range(-99999.9999, 0.0000)
-        self.cutz_entry.setObjectName(_("Cut Z"))
+        self.cutz_entry.setObjectName("i_vcutz")
 
 
         self.grid3.addWidget(cutzlabel, 5, 0)
         self.grid3.addWidget(cutzlabel, 5, 0)
         self.grid3.addWidget(self.cutz_entry, 5, 1)
         self.grid3.addWidget(self.cutz_entry, 5, 1)
@@ -269,7 +269,7 @@ class ToolIsolation(AppTool, Gerber):
         self.addtool_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.addtool_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.addtool_entry.set_precision(self.decimals)
         self.addtool_entry.set_precision(self.decimals)
         self.addtool_entry.set_range(0.000, 9999.9999)
         self.addtool_entry.set_range(0.000, 9999.9999)
-        self.addtool_entry.setObjectName(_("Tool Dia"))
+        self.addtool_entry.setObjectName("i_new_tooldia")
 
 
         self.grid3.addWidget(self.addtool_entry_lbl, 6, 0)
         self.grid3.addWidget(self.addtool_entry_lbl, 6, 0)
         self.grid3.addWidget(self.addtool_entry, 6, 1)
         self.grid3.addWidget(self.addtool_entry, 6, 1)
@@ -402,7 +402,7 @@ class ToolIsolation(AppTool, Gerber):
         self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
         self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
                                         {'label': _('Ext'), 'value': 'ext'},
                                         {'label': _('Ext'), 'value': 'ext'},
                                         {'label': _('Int'), 'value': 'int'}])
                                         {'label': _('Int'), 'value': 'int'}])
-        self.iso_type_radio.setObjectName("i_type")
+        self.iso_type_radio.setObjectName("i_iso_type")
 
 
         self.grid3.addWidget(self.iso_type_label, 17, 0)
         self.grid3.addWidget(self.iso_type_label, 17, 0)
         self.grid3.addWidget(self.iso_type_radio, 17, 1)
         self.grid3.addWidget(self.iso_type_radio, 17, 1)
@@ -432,8 +432,8 @@ class ToolIsolation(AppTool, Gerber):
         self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
         self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
 
 
         # Rest Machining
         # Rest Machining
-        self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
-        self.rest_cb.setObjectName("i_rest_machining")
+        self.rest_cb = FCCheckBox('%s' % _("Rest"))
+        self.rest_cb.setObjectName("i_rest")
         self.rest_cb.setToolTip(
         self.rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
             _("If checked, use 'rest machining'.\n"
               "Basically it will isolate outside PCB features,\n"
               "Basically it will isolate outside PCB features,\n"
@@ -677,21 +677,21 @@ class ToolIsolation(AppTool, Gerber):
         self.tooldia = None
         self.tooldia = None
 
 
         self.form_fields = {
         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
+            "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 = {
         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_type": "tools_iso_isotype"
+            "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
         self.old_tool_dia = None
@@ -715,7 +715,7 @@ class ToolIsolation(AppTool, Gerber):
 
 
         self.type_excobj_radio.activated_custom.connect(self.on_type_excobj_index_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.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
-        self.addtool_from_db_btn.clicked.connect(self.on_ncc_tool_add_from_db_clicked)
+        self.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked)
 
 
         self.generate_iso_button.clicked.connect(self.on_isolate_click)
         self.generate_iso_button.clicked.connect(self.on_isolate_click)
         self.reset_button.clicked.connect(self.set_tool_ui)
         self.reset_button.clicked.connect(self.set_tool_ui)
@@ -731,21 +731,6 @@ class ToolIsolation(AppTool, Gerber):
             "gerber": "Gerber", "geometry": "Geometry"
             "gerber": "Gerber", "geometry": "Geometry"
         }[self.type_excobj_radio.get_value()]
         }[self.type_excobj_radio.get_value()]
 
 
-    def on_operation_change(self, val):
-        if val == 'iso':
-            self.milling_type_label.setEnabled(True)
-            self.milling_type_radio.setEnabled(True)
-        else:
-            self.milling_type_label.setEnabled(False)
-            self.milling_type_radio.setEnabled(False)
-
-        current_row = self.tools_table.currentRow()
-        try:
-            current_uid = int(self.tools_table.item(current_row, 3).text())
-            self.iso_tools[current_uid]['data']['tools_nccoperation'] = val
-        except AttributeError:
-            return
-
     def on_row_selection_change(self):
     def on_row_selection_change(self):
         self.blockSignals(True)
         self.blockSignals(True)
 
 
@@ -782,7 +767,7 @@ class ToolIsolation(AppTool, Gerber):
                                     form_value_storage = tooluid_value[key]
                                     form_value_storage = tooluid_value[key]
                                     self.storage_to_form(form_value_storage)
                                     self.storage_to_form(form_value_storage)
                 except Exception as e:
                 except Exception as e:
-                    log.debug("NonCopperClear ---> update_ui() " + str(e))
+                    log.debug("ToolIsolation ---> update_ui() " + str(e))
             else:
             else:
                 self.tool_data_label.setText(
                 self.tool_data_label.setText(
                     "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
                     "<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
@@ -797,7 +782,7 @@ class ToolIsolation(AppTool, Gerber):
                     try:
                     try:
                         self.form_fields[form_key].set_value(dict_storage[form_key])
                         self.form_fields[form_key].set_value(dict_storage[form_key])
                     except Exception as e:
                     except Exception as e:
-                        log.debug("NonCopperClear.storage_to_form() --> %s" % str(e))
+                        log.debug("ToolIsolation.storage_to_form() --> %s" % str(e))
                         pass
                         pass
 
 
     def form_to_storage(self):
     def form_to_storage(self):
@@ -831,7 +816,7 @@ class ToolIsolation(AppTool, Gerber):
     def on_apply_param_to_all_clicked(self):
     def on_apply_param_to_all_clicked(self):
         if self.tools_table.rowCount() == 0:
         if self.tools_table.rowCount() == 0:
             # there is no tool in tool table so we can't save the GUI elements values to storage
             # there is no tool in tool table so we can't save the GUI elements values to storage
-            log.debug("NonCopperClear.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.")
+            log.debug("ToolIsolation.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.")
             return
             return
 
 
         self.blockSignals(True)
         self.blockSignals(True)
@@ -853,47 +838,7 @@ class ToolIsolation(AppTool, Gerber):
         for tooluid_key, tooluid_val in self.iso_tools.items():
         for tooluid_key, tooluid_val in self.iso_tools.items():
             tooluid_val['data'] = deepcopy(temp_tool_data)
             tooluid_val['data'] = deepcopy(temp_tool_data)
 
 
-        # store all the data associated with the row parameter to the self.tools storage
-        # tooldia_item = float(self.tools_table.item(row, 1).text())
-        # type_item = self.tools_table.cellWidget(row, 2).currentText()
-        # operation_type_item = self.tools_table.cellWidget(row, 4).currentText()
-        #
-        # nccoffset_item = self.ncc_choice_offset_cb.get_value()
-        # nccoffset_value_item = float(self.ncc_offset_spinner.get_value())
-
-        # this new dict will hold the actual useful data, another dict that is the value of key 'data'
-        # temp_tools = {}
-        # temp_dia = {}
-        # temp_data = {}
-        #
-        # for tooluid_key, tooluid_value in self.iso_tools.items():
-        #     for key, value in tooluid_value.items():
-        #         if key == 'data':
-        #             # update the 'data' section
-        #             for data_key in tooluid_value[key].keys():
-        #                 for form_key, form_value in self.form_fields.items():
-        #                     if form_key == data_key:
-        #                         temp_data[data_key] = form_value.get_value()
-        #                 # make sure we make a copy of the keys not in the form (we may use 'data' keys that are
-        #                 # updated from self.app.defaults
-        #                 if data_key not in self.form_fields:
-        #                     temp_data[data_key] = value[data_key]
-        #             temp_dia[key] = deepcopy(temp_data)
-        #             temp_data.clear()
-        #
-        #         elif key == 'solid_geometry':
-        #             temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry'])
-        #         else:
-        #             temp_dia[key] = deepcopy(value)
-        #
-        #         temp_tools[tooluid_key] = deepcopy(temp_dia)
-        #
-        # self.iso_tools.clear()
-        # self.iso_tools = deepcopy(temp_tools)
-        # temp_tools.clear()
-
         self.app.inform.emit('[success] %s' % _("Current Tool parameters were applied to all tools."))
         self.app.inform.emit('[success] %s' % _("Current Tool parameters were applied to all tools."))
-
         self.blockSignals(False)
         self.blockSignals(False)
 
 
     def on_add_tool_by_key(self):
     def on_add_tool_by_key(self):
@@ -979,14 +924,14 @@ class ToolIsolation(AppTool, Gerber):
             self.iso_type_radio.set_value('full')
             self.iso_type_radio.set_value('full')
             self.iso_type_radio.hide()
             self.iso_type_radio.hide()
 
 
-            self.follow_cb.setChecked(False)
+            self.follow_cb.set_value(False)
             self.follow_cb.hide()
             self.follow_cb.hide()
             self.follow_label.hide()
             self.follow_label.hide()
 
 
-            self.rest_cb.setChecked(False)
+            self.rest_cb.set_value(False)
             self.rest_cb.hide()
             self.rest_cb.hide()
 
 
-            self.except_cb.setChecked(False)
+            self.except_cb.set_value(False)
             self.except_cb.hide()
             self.except_cb.hide()
 
 
             self.select_combo.setCurrentIndex(0)
             self.select_combo.setCurrentIndex(0)
@@ -995,7 +940,6 @@ class ToolIsolation(AppTool, Gerber):
         else:
         else:
             self.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
             self.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
 
 
-            # TODO remember to set the GUI elements to values from app.defaults dict
             self.tool_type_radio.set_value(self.app.defaults["tools_iso_tool_type"])
             self.tool_type_radio.set_value(self.app.defaults["tools_iso_tool_type"])
             self.tool_type_label.show()
             self.tool_type_label.show()
             self.tool_type_radio.show()
             self.tool_type_radio.show()
@@ -1007,17 +951,17 @@ class ToolIsolation(AppTool, Gerber):
             self.iso_type_radio.set_value(self.app.defaults["tools_iso_isotype"])
             self.iso_type_radio.set_value(self.app.defaults["tools_iso_isotype"])
             self.iso_type_radio.show()
             self.iso_type_radio.show()
 
 
-            self.follow_cb.setChecked(self.app.defaults["tools_iso_follow"])
+            self.follow_cb.set_value(self.app.defaults["tools_iso_follow"])
             self.follow_cb.show()
             self.follow_cb.show()
             self.follow_label.show()
             self.follow_label.show()
 
 
-            self.rest_cb.setChecked(self.app.defaults["tools_iso_rest"])
+            self.rest_cb.set_value(self.app.defaults["tools_iso_rest"])
             self.rest_cb.show()
             self.rest_cb.show()
 
 
-            self.except_cb.setChecked(self.app.defaults["tools_iso_isoexcept"])
+            self.except_cb.set_value(self.app.defaults["tools_iso_isoexcept"])
             self.except_cb.show()
             self.except_cb.show()
 
 
-            self.select_combo.setCurrentIndex(self.app.defaults["tools_iso_selection"])
+            self.select_combo.set_value(self.app.defaults["tools_iso_selection"])
             self.select_combo.show()
             self.select_combo.show()
             self.select_label.show()
             self.select_label.show()
 
 
@@ -1185,21 +1129,12 @@ class ToolIsolation(AppTool, Gerber):
 
 
                     tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
                     tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
 
 
-                    # operation_type = FCComboBox()
-                    # operation_type.addItems(['iso_op', 'clear_op'])
-                    #
-                    # # operation_type.setStyleSheet('background-color: rgb(255,255,255)')
-                    # op_idx = operation_type.findText(tooluid_value['operation'])
-                    # operation_type.setCurrentIndex(op_idx)
-
                     self.tools_table.setItem(row_no, 1, dia)  # Diameter
                     self.tools_table.setItem(row_no, 1, dia)  # Diameter
                     self.tools_table.setCellWidget(row_no, 2, tool_type_item)
                     self.tools_table.setCellWidget(row_no, 2, tool_type_item)
 
 
                     # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
                     # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
                     self.tools_table.setItem(row_no, 3, tool_uid_item)  # Tool unique ID
                     self.tools_table.setItem(row_no, 3, tool_uid_item)  # Tool unique ID
 
 
-                    # self.tools_table.setCellWidget(row_no, 4, operation_type)
-
         # make the diameter column editable
         # make the diameter column editable
         for row in range(tool_id):
         for row in range(tool_id):
             self.tools_table.item(row, 1).setFlags(
             self.tools_table.item(row, 1).setFlags(
@@ -1746,6 +1681,596 @@ class ToolIsolation(AppTool, Gerber):
                               isotooldia=self.iso_dia_list,
                               isotooldia=self.iso_dia_list,
                               outname=self.o_name)
                               outname=self.o_name)
 
 
+
+
+
+    # ###########################################
+    # ###########################################
+    # ###########################################
+    # ###########################################
+
+    def on_iso_button_click(self, *args):
+
+        obj = self.app.collection.get_active()
+
+        self.iso_type = 2
+        if self.ui.iso_type_radio.get_value() == 'ext':
+            self.iso_type = 0
+        if self.ui.iso_type_radio.get_value() == 'int':
+            self.iso_type = 1
+
+        def worker_task(iso_obj, app_obj):
+            with self.app.proc_container.new(_("Isolating...")):
+                if self.ui.follow_cb.get_value() is True:
+                    iso_obj.follow_geo()
+                    # in the end toggle the visibility of the origin object so we can see the generated Geometry
+                    iso_obj.ui.plot_cb.toggle()
+                else:
+                    app_obj.defaults.report_usage("gerber_on_iso_button")
+                    self.read_form()
+
+                    iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single'
+                    self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
+
+        self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
+
+    def follow_geo(self, outname=None):
+        """
+        Creates a geometry object "following" the gerber paths.
+
+        :return: None
+        """
+
+        # default_name = self.options["name"] + "_follow"
+        # follow_name = outname or default_name
+
+        if outname is None:
+            follow_name = self.options["name"] + "_follow"
+        else:
+            follow_name = outname
+
+        def follow_init(follow_obj, app):
+            # Propagate options
+            follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
+            follow_obj.solid_geometry = self.follow_geometry
+
+        # TODO: Do something if this is None. Offer changing name?
+        try:
+            self.app.app_obj.new_object("geometry", follow_name, follow_init)
+        except Exception as e:
+            return "Operation failed: %s" % str(e)
+
+    def isolate_handler(self, iso_type, iso_scope):
+
+        if iso_scope == 'all':
+            self.isolate(iso_type=iso_type)
+        else:
+            # disengage the grid snapping since it may be hard to click on polygons with grid snapping on
+            if self.app.ui.grid_snap_btn.isChecked():
+                self.grid_status_memory = True
+                self.app.ui.grid_snap_btn.trigger()
+            else:
+                self.grid_status_memory = False
+
+            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
+
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
+
+    def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
+                milling_type=None, follow=None, plot=True):
+        """
+        Creates an isolation routing geometry object in the project.
+
+        :param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
+        :param geometry: specific geometry to isolate
+        :param dia: Tool diameter
+        :param passes: Number of tool widths to cut
+        :param overlap: Overlap between passes in fraction of tool diameter
+        :param outname: Base name of the output object
+        :param combine: Boolean: if to combine passes in one resulting object in case of multiple passes
+        :param milling_type: type of milling: conventional or climbing
+        :param follow: Boolean: if to generate a 'follow' geometry
+        :param plot: Boolean: if to plot the resulting geometry object
+        :return: None
+        """
+
+        if geometry is None:
+            work_geo = self.follow_geometry if follow is True else self.solid_geometry
+        else:
+            work_geo = geometry
+
+        if dia is None:
+            dia = float(self.options["isotooldia"])
+
+        if passes is None:
+            passes = int(self.options["isopasses"])
+
+        if overlap is None:
+            overlap = float(self.options["isooverlap"])
+
+        overlap /= 100.0
+
+        combine = self.options["combine_passes"] if combine is None else bool(combine)
+
+        if milling_type is None:
+            milling_type = self.options["milling_type"]
+
+        if iso_type is None:
+            iso_t = 2
+        else:
+            iso_t = iso_type
+
+        base_name = self.options["name"]
+
+        if combine:
+            if outname is None:
+                if self.iso_type == 0:
+                    iso_name = base_name + "_ext_iso"
+                elif self.iso_type == 1:
+                    iso_name = base_name + "_int_iso"
+                else:
+                    iso_name = base_name + "_iso"
+            else:
+                iso_name = outname
+
+            def iso_init(geo_obj, app_obj):
+                # Propagate options
+                geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
+                geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
+
+                geo_obj.solid_geometry = []
+
+                # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
+                if self.ui.tool_type_radio.get_value() == 'v':
+                    new_cutz = self.ui.cutz_spinner.get_value()
+                    new_vtipdia = self.ui.tipdia_spinner.get_value()
+                    new_vtipangle = self.ui.tipangle_spinner.get_value()
+                    tool_type = 'V'
+                else:
+                    new_cutz = self.app.defaults['geometry_cutz']
+                    new_vtipdia = self.app.defaults['geometry_vtipdia']
+                    new_vtipangle = self.app.defaults['geometry_vtipangle']
+                    tool_type = 'C1'
+
+                # store here the default data for Geometry Data
+                default_data = {}
+                default_data.update({
+                    "name": iso_name,
+                    "plot": self.app.defaults['geometry_plot'],
+                    "cutz": new_cutz,
+                    "vtipdia": new_vtipdia,
+                    "vtipangle": new_vtipangle,
+                    "travelz": self.app.defaults['geometry_travelz'],
+                    "feedrate": self.app.defaults['geometry_feedrate'],
+                    "feedrate_z": self.app.defaults['geometry_feedrate_z'],
+                    "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
+                    "dwell": self.app.defaults['geometry_dwell'],
+                    "dwelltime": self.app.defaults['geometry_dwelltime'],
+                    "multidepth": self.app.defaults['geometry_multidepth'],
+                    "ppname_g": self.app.defaults['geometry_ppname_g'],
+                    "depthperpass": self.app.defaults['geometry_depthperpass'],
+                    "extracut": self.app.defaults['geometry_extracut'],
+                    "extracut_length": self.app.defaults['geometry_extracut_length'],
+                    "toolchange": self.app.defaults['geometry_toolchange'],
+                    "toolchangez": self.app.defaults['geometry_toolchangez'],
+                    "endz": self.app.defaults['geometry_endz'],
+                    "spindlespeed": self.app.defaults['geometry_spindlespeed'],
+                    "toolchangexy": self.app.defaults['geometry_toolchangexy'],
+                    "startz": self.app.defaults['geometry_startz']
+                })
+
+                geo_obj.tools = {}
+                geo_obj.tools['1'] = {}
+                geo_obj.tools.update({
+                    '1': {
+                        'tooldia': float(self.options["isotooldia"]),
+                        'offset': 'Path',
+                        'offset_value': 0.0,
+                        'type': _('Rough'),
+                        'tool_type': tool_type,
+                        'data': default_data,
+                        'solid_geometry': geo_obj.solid_geometry
+                    }
+                })
+
+                for nr_pass in range(passes):
+                    iso_offset = dia * ((2 * nr_pass + 1) / 2.0000001) - (nr_pass * overlap * dia)
+
+                    # if milling type is climb then the move is counter-clockwise around features
+                    mill_dir = 1 if milling_type == 'cl' else 0
+                    geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
+                                                  follow=follow, nr_passes=nr_pass)
+
+                    if geom == 'fail':
+                        app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
+                        return 'fail'
+                    geo_obj.solid_geometry.append(geom)
+
+                    # update the geometry in the tools
+                    geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry
+
+                # detect if solid_geometry is empty and this require list flattening which is "heavy"
+                # or just looking in the lists (they are one level depth) and if any is not empty
+                # proceed with object creation, if there are empty and the number of them is the length
+                # of the list then we have an empty solid_geometry which should raise a Custom Exception
+                empty_cnt = 0
+                if not isinstance(geo_obj.solid_geometry, list) and \
+                        not isinstance(geo_obj.solid_geometry, MultiPolygon):
+                    geo_obj.solid_geometry = [geo_obj.solid_geometry]
+
+                for g in geo_obj.solid_geometry:
+                    if g:
+                        break
+                    else:
+                        empty_cnt += 1
+
+                if empty_cnt == len(geo_obj.solid_geometry):
+                    raise ValidationError("Empty Geometry", None)
+                else:
+                    app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
+
+                # even if combine is checked, one pass is still single-geo
+                geo_obj.multigeo = True if passes > 1 else False
+
+                # ############################################################
+                # ########## AREA SUBTRACTION ################################
+                # ############################################################
+                if self.ui.except_cb.get_value():
+                    self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                    geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
+
+            # TODO: Do something if this is None. Offer changing name?
+            self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
+        else:
+            for i in range(passes):
+                offset = dia * ((2 * i + 1) / 2.0000001) - (i * overlap * dia)
+                if passes > 1:
+                    if outname is None:
+                        if self.iso_type == 0:
+                            iso_name = base_name + "_ext_iso" + str(i + 1)
+                        elif self.iso_type == 1:
+                            iso_name = base_name + "_int_iso" + str(i + 1)
+                        else:
+                            iso_name = base_name + "_iso" + str(i + 1)
+                    else:
+                        iso_name = outname
+                else:
+                    if outname is None:
+                        if self.iso_type == 0:
+                            iso_name = base_name + "_ext_iso"
+                        elif self.iso_type == 1:
+                            iso_name = base_name + "_int_iso"
+                        else:
+                            iso_name = base_name + "_iso"
+                    else:
+                        iso_name = outname
+
+                def iso_init(geo_obj, fc_obj):
+                    # Propagate options
+                    geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
+                    if self.ui.tool_type_radio.get_value() == 'v':
+                        geo_obj.tool_type = 'V'
+                    else:
+                        geo_obj.tool_type = 'C1'
+
+                    # if milling type is climb then the move is counter-clockwise around features
+                    mill_dir = 1 if milling_type == 'cl' else 0
+                    geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
+                                                  follow=follow,
+                                                  nr_passes=i)
+
+                    if geom == 'fail':
+                        fc_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
+                        return 'fail'
+
+                    geo_obj.solid_geometry = geom
+
+                    # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
+                    # even if the resulting geometry is not multigeo we add the tools dict which will hold the data
+                    # required to be transfered to the Geometry object
+                    if self.ui.tool_type_radio.get_value() == 'v':
+                        new_cutz = self.ui.cutz_spinner.get_value()
+                        new_vtipdia = self.ui.tipdia_spinner.get_value()
+                        new_vtipangle = self.ui.tipangle_spinner.get_value()
+                        tool_type = 'V'
+                    else:
+                        new_cutz = self.app.defaults['geometry_cutz']
+                        new_vtipdia = self.app.defaults['geometry_vtipdia']
+                        new_vtipangle = self.app.defaults['geometry_vtipangle']
+                        tool_type = 'C1'
+
+                    # store here the default data for Geometry Data
+                    default_data = {}
+                    default_data.update({
+                        "name": iso_name,
+                        "plot": self.app.defaults['geometry_plot'],
+                        "cutz": new_cutz,
+                        "vtipdia": new_vtipdia,
+                        "vtipangle": new_vtipangle,
+                        "travelz": self.app.defaults['geometry_travelz'],
+                        "feedrate": self.app.defaults['geometry_feedrate'],
+                        "feedrate_z": self.app.defaults['geometry_feedrate_z'],
+                        "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
+                        "dwell": self.app.defaults['geometry_dwell'],
+                        "dwelltime": self.app.defaults['geometry_dwelltime'],
+                        "multidepth": self.app.defaults['geometry_multidepth'],
+                        "ppname_g": self.app.defaults['geometry_ppname_g'],
+                        "depthperpass": self.app.defaults['geometry_depthperpass'],
+                        "extracut": self.app.defaults['geometry_extracut'],
+                        "extracut_length": self.app.defaults['geometry_extracut_length'],
+                        "toolchange": self.app.defaults['geometry_toolchange'],
+                        "toolchangez": self.app.defaults['geometry_toolchangez'],
+                        "endz": self.app.defaults['geometry_endz'],
+                        "spindlespeed": self.app.defaults['geometry_spindlespeed'],
+                        "toolchangexy": self.app.defaults['geometry_toolchangexy'],
+                        "startz": self.app.defaults['geometry_startz']
+                    })
+
+                    geo_obj.tools = {}
+                    geo_obj.tools['1'] = {}
+                    geo_obj.tools.update({
+                        '1': {
+                            'tooldia': float(self.options["isotooldia"]),
+                            'offset': 'Path',
+                            'offset_value': 0.0,
+                            'type': _('Rough'),
+                            'tool_type': tool_type,
+                            'data': default_data,
+                            'solid_geometry': geo_obj.solid_geometry
+                        }
+                    })
+
+                    # detect if solid_geometry is empty and this require list flattening which is "heavy"
+                    # or just looking in the lists (they are one level depth) and if any is not empty
+                    # proceed with object creation, if there are empty and the number of them is the length
+                    # of the list then we have an empty solid_geometry which should raise a Custom Exception
+                    empty_cnt = 0
+                    if not isinstance(geo_obj.solid_geometry, list):
+                        geo_obj.solid_geometry = [geo_obj.solid_geometry]
+
+                    for g in geo_obj.solid_geometry:
+                        if g:
+                            break
+                        else:
+                            empty_cnt += 1
+
+                    if empty_cnt == len(geo_obj.solid_geometry):
+                        raise ValidationError("Empty Geometry", None)
+                    else:
+                        fc_obj.inform.emit('[success] %s: %s' %
+                                            (_("Isolation geometry created"), geo_obj.options["name"]))
+                    geo_obj.multigeo = False
+
+                    # ############################################################
+                    # ########## AREA SUBTRACTION ################################
+                    # ############################################################
+                    if self.ui.except_cb.get_value():
+                        self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
+                        geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
+
+                # TODO: Do something if this is None. Offer changing name?
+                self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
+
+    def area_subtraction(self, geo, subtractor_geo=None):
+        """
+        Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo
+
+        :param geo: target geometry from which to subtract
+        :param subtractor_geo: geometry that acts as subtractor
+        :return:
+        """
+        new_geometry = []
+        target_geo = geo
+
+        if subtractor_geo:
+            sub_union = cascaded_union(subtractor_geo)
+        else:
+            name = self.ui.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):
+                    new_geo = geo_elem.difference(sub_union)
+                    if new_geo:
+                        if not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+                elif isinstance(geo_elem, MultiLineString):
+                    for line_elem in geo_elem:
+                        new_geo = line_elem.difference(sub_union)
+                        if new_geo and not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+        except TypeError:
+            if isinstance(target_geo, Polygon):
+                for ring in self.poly2rings(target_geo):
+                    new_geo = ring.difference(sub_union)
+                    if new_geo:
+                        if not new_geo.is_empty:
+                            new_geometry.append(new_geo)
+            elif isinstance(target_geo, LineString):
+                new_geo = target_geo.difference(sub_union)
+                if new_geo and not new_geo.is_empty:
+                    new_geometry.append(new_geo)
+            elif isinstance(target_geo, MultiLineString):
+                for line_elem in target_geo:
+                    new_geo = line_elem.difference(sub_union)
+                    if new_geo and not new_geo.is_empty:
+                        new_geometry.append(new_geo)
+        return new_geometry
+
+    def on_mouse_click_release(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            right_button = 2
+            self.app.event_is_dragging = self.app.event_is_dragging
+        else:
+            event_pos = (event.xdata, event.ydata)
+            right_button = 3
+            self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
+
+        try:
+            x = float(event_pos[0])
+            y = float(event_pos[1])
+        except TypeError:
+            return
+
+        event_pos = (x, y)
+        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+        if self.app.grid_status():
+            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+        else:
+            curr_pos = (curr_pos[0], curr_pos[1])
+
+        if event.button == 1:
+            clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
+
+            if self.app.selection_type is not None:
+                self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
+                self.app.selection_type = None
+            elif clicked_poly:
+                if clicked_poly not in self.poly_dict.values():
+                    shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
+                                                        color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                        face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                        visible=True)
+                    self.poly_dict[shape_id] = clicked_poly
+                    self.app.inform.emit(
+                        '%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)),
+                                        _("Click to add next polygon or right click to start isolation."))
+                    )
+                else:
+                    try:
+                        for k, v in list(self.poly_dict.items()):
+                            if v == clicked_poly:
+                                self.app.tool_shapes.remove(k)
+                                self.poly_dict.pop(k)
+                                break
+                    except TypeError:
+                        return
+                    self.app.inform.emit(
+                        '%s. %s' % (_("Removed polygon"),
+                                    _("Click to add/remove next polygon or right click to start isolation."))
+                    )
+
+                self.app.tool_shapes.redraw()
+            else:
+                self.app.inform.emit(_("No polygon detected under click position."))
+        elif event.button == right_button and self.app.event_is_dragging is False:
+            # restore the Grid snapping if it was active before
+            if self.grid_status_memory is True:
+                self.app.ui.grid_snap_btn.trigger()
+
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
+
+            self.app.tool_shapes.clear(update=True)
+
+            if self.poly_dict:
+                poly_list = deepcopy(list(self.poly_dict.values()))
+                self.isolate(iso_type=self.iso_type, geometry=poly_list)
+                self.poly_dict.clear()
+            else:
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
+
+    def selection_area_handler(self, start_pos, end_pos, sel_type):
+        """
+        :param start_pos: mouse position when the selection LMB click was done
+        :param end_pos: mouse position when the left mouse button is released
+        :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
+        :return:
+        """
+        poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
+
+        # delete previous selection shape
+        self.app.delete_selection_shape()
+
+        added_poly_count = 0
+        try:
+            for geo in self.solid_geometry:
+                if geo not in self.poly_dict.values():
+                    if sel_type is True:
+                        if geo.within(poly_selection):
+                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                                shape=geo,
+                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                                face_color=self.app.defaults[
+                                                                               'global_sel_draw_color'] + 'AF',
+                                                                visible=True)
+                            self.poly_dict[shape_id] = geo
+                            added_poly_count += 1
+                    else:
+                        if poly_selection.intersects(geo):
+                            shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                                shape=geo,
+                                                                color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                                face_color=self.app.defaults[
+                                                                               'global_sel_draw_color'] + 'AF',
+                                                                visible=True)
+                            self.poly_dict[shape_id] = geo
+                            added_poly_count += 1
+        except TypeError:
+            if self.solid_geometry not in self.poly_dict.values():
+                if sel_type is True:
+                    if self.solid_geometry.within(poly_selection):
+                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                            shape=self.solid_geometry,
+                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                            face_color=self.app.defaults[
+                                                                           'global_sel_draw_color'] + 'AF',
+                                                            visible=True)
+                        self.poly_dict[shape_id] = self.solid_geometry
+                        added_poly_count += 1
+                else:
+                    if poly_selection.intersects(self.solid_geometry):
+                        shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
+                                                            shape=self.solid_geometry,
+                                                            color=self.app.defaults['global_sel_draw_color'] + 'AF',
+                                                            face_color=self.app.defaults[
+                                                                           'global_sel_draw_color'] + 'AF',
+                                                            visible=True)
+                        self.poly_dict[shape_id] = self.solid_geometry
+                        added_poly_count += 1
+
+        if added_poly_count > 0:
+            self.app.tool_shapes.redraw()
+            self.app.inform.emit(
+                '%s: %d. %s' % (_("Added polygon"),
+                                int(added_poly_count),
+                                _("Click to add next polygon or right click to start isolation."))
+            )
+        else:
+            self.app.inform.emit(_("No polygon in selection."))
+
+    # ###########################################
+    # ###########################################
+    # ###########################################
+    # ###########################################
+
+
+
+
     # To be called after clicking on the plot.
     # To be called after clicking on the plot.
     def on_mouse_release(self, event):
     def on_mouse_release(self, event):
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
@@ -2802,14 +3327,14 @@ class ToolIsolation(AppTool, Gerber):
                 return 'fail'
                 return 'fail'
         return geom
         return geom
 
 
-    def on_ncc_tool_add_from_db_executed(self, tool):
+    def on_tool_add_from_db_executed(self, tool):
         """
         """
         Here add the tool from DB  in the selected geometry object
         Here add the tool from DB  in the selected geometry object
         :return:
         :return:
         """
         """
         tool_from_db = deepcopy(tool)
         tool_from_db = deepcopy(tool)
 
 
-        res = self.on_ncc_tool_from_db_inserted(tool=tool_from_db)
+        res = self.on_tool_from_db_inserted(tool=tool_from_db)
 
 
         for idx in range(self.app.ui.plot_tab_area.count()):
         for idx in range(self.app.ui.plot_tab_area.count()):
             if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
             if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
@@ -2828,7 +3353,7 @@ class ToolIsolation(AppTool, Gerber):
                 self.tools_table.selectRow(row)
                 self.tools_table.selectRow(row)
         self.on_row_selection_change()
         self.on_row_selection_change()
 
 
-    def on_ncc_tool_from_db_inserted(self, tool):
+    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
         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
         :param tool: a dict with the tool data
@@ -2887,7 +3412,7 @@ class ToolIsolation(AppTool, Gerber):
         # if self.tools_table.rowCount() != 0:
         # if self.tools_table.rowCount() != 0:
         #     self.param_frame.setDisabled(False)
         #     self.param_frame.setDisabled(False)
 
 
-    def on_ncc_tool_add_from_db_clicked(self):
+    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
         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
         and display the Tools Database tab in the form needed for the Tool adding
@@ -2917,7 +3442,7 @@ class ToolIsolation(AppTool, Gerber):
         self.cursor_pos = None
         self.cursor_pos = None
         self.mouse_is_dragging = False
         self.mouse_is_dragging = False
 
 
-        prog_plot = True if self.app.defaults["tools_ncc_plotting"] == 'progressive' else False
+        prog_plot = True if self.app.defaults["tools_iso_plotting"] == 'progressive' else False
         if prog_plot:
         if prog_plot:
             self.temp_shapes.clear(update=True)
             self.temp_shapes.clear(update=True)
 
 

+ 1 - 1
AppTools/ToolNCC.py

@@ -509,7 +509,7 @@ class NonCopperClear(AppTool, Gerber):
         self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
         self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
 
 
         # Rest Machining
         # Rest Machining
-        self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining"))
+        self.ncc_rest_cb = FCCheckBox('%s' % _("Rest"))
         self.ncc_rest_cb.setObjectName("n_rest_machining")
         self.ncc_rest_cb.setObjectName("n_rest_machining")
 
 
         self.ncc_rest_cb.setToolTip(
         self.ncc_rest_cb.setToolTip(

+ 1 - 1
AppTools/ToolPaint.py

@@ -438,7 +438,7 @@ class ToolPaint(AppTool, Gerber):
         )
         )
         grid4.addWidget(self.gen_param_label, 15, 0, 1, 2)
         grid4.addWidget(self.gen_param_label, 15, 0, 1, 2)
 
 
-        self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
+        self.rest_cb = FCCheckBox('%s' % _("Rest"))
         self.rest_cb.setObjectName('p_rest_machining')
         self.rest_cb.setObjectName('p_rest_machining')
         self.rest_cb.setToolTip(
         self.rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
             _("If checked, use 'rest machining'.\n"

+ 4 - 0
CHANGELOG.md

@@ -14,6 +14,10 @@ CHANGELOG for FlatCAM beta
 - if there is a Gerber object selected then in Isolation Tool the Gerber object combobox will show that object name as current
 - if there is a Gerber object selected then in Isolation Tool the Gerber object combobox will show that object name as current
 - made the Project Tree items not editable by clicking on selected Tree items (the object rename can still be done in the Selected tab)
 - made the Project Tree items not editable by clicking on selected Tree items (the object rename can still be done in the Selected tab)
 - working on Isolation Tool: added a Preferences section in Edit -> Preferences and updated their usage within the Isolation tool
 - 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
 
 
 25.05.2020
 25.05.2020
 
 

+ 1 - 1
camlib.py

@@ -4374,7 +4374,7 @@ class CNCjob(Geometry):
 
 
                     self.gcode += self.create_gcode_multi_pass(geo, current_tooldia, extracut, self.extracut_length,
                     self.gcode += self.create_gcode_multi_pass(geo, current_tooldia, extracut, self.extracut_length,
                                                                tolerance,
                                                                tolerance,
-                                                               z_move=z_move,_postproc=p,
+                                                               z_move=z_move, postproc=p,
                                                                old_point=current_pt)
                                                                old_point=current_pt)
 
 
                 # calculate the travel distance
                 # calculate the travel distance

+ 6 - 5
defaults.py

@@ -395,11 +395,12 @@ class FlatCAMDefaults:
         "tools_iso_follow": False,
         "tools_iso_follow": False,
         "tools_iso_isotype": "full",
         "tools_iso_isotype": "full",
 
 
-        "tools_iso_rest": False,
+        "tools_iso_rest":           False,
         "tools_iso_combine_passes": False,
         "tools_iso_combine_passes": False,
-        "tools_iso_isoexcept": False,
-        "tools_iso_selection": _("All"),
-        "tools_iso_area_shape": "square",
+        "tools_iso_isoexcept":      False,
+        "tools_iso_selection":      _("All"),
+        "tools_iso_area_shape":     "square",
+        "tools_iso_plotting":       'normal',
 
 
         # NCC Tool
         # NCC Tool
         "tools_ncctools": "1.0, 0.5",
         "tools_ncctools": "1.0, 0.5",
@@ -415,13 +416,13 @@ class FlatCAMDefaults:
         "tools_ncc_offset_value": 0.0000,
         "tools_ncc_offset_value": 0.0000,
         "tools_nccref": _('Itself'),
         "tools_nccref": _('Itself'),
         "tools_ncc_area_shape": "square",
         "tools_ncc_area_shape": "square",
-        "tools_ncc_plotting": 'normal',
         "tools_nccmilling_type": 'cl',
         "tools_nccmilling_type": 'cl',
         "tools_ncctool_type": 'C1',
         "tools_ncctool_type": 'C1',
         "tools_ncccutz": -0.05,
         "tools_ncccutz": -0.05,
         "tools_ncctipdia": 0.1,
         "tools_ncctipdia": 0.1,
         "tools_ncctipangle": 30,
         "tools_ncctipangle": 30,
         "tools_nccnewdia": 0.1,
         "tools_nccnewdia": 0.1,
+        "tools_ncc_plotting": 'normal',
 
 
         # Cutout Tool
         # Cutout Tool
         "tools_cutouttooldia": 2.4,
         "tools_cutouttooldia": 2.4,