Ver código fonte

- in Tools: Film, Image, InvertGerber, Optimal, PcbWizard - moved the Tool UI in its own class

Marius Stanciu 5 anos atrás
pai
commit
c45444a772
6 arquivos alterados com 823 adições e 716 exclusões
  1. 1 0
      CHANGELOG.md
  2. 25 24
      appTools/ToolFilm.py
  3. 170 132
      appTools/ToolImage.py
  4. 161 154
      appTools/ToolInvertGerber.py
  5. 283 255
      appTools/ToolOptimal.py
  6. 183 151
      appTools/ToolPcbWizard.py

+ 1 - 0
CHANGELOG.md

@@ -16,6 +16,7 @@ CHANGELOG for FlatCAM beta
 - in Tool Cutout: modified the UI in preparation for adding the Mouse Bites feature
 - in Tool Cutout: modified the UI in preparation for adding the Mouse Bites feature
 - Turkish translation strings were updated by the translator, Mehmet Kaya
 - Turkish translation strings were updated by the translator, Mehmet Kaya
 - Film Tool - moved the Tool UI in its own class
 - Film Tool - moved the Tool UI in its own class
+- in Tools: Film, Image, InvertGerber, Optimal, PcbWizard - moved the Tool UI in its own class
 
 
 26.08.2020
 26.08.2020
 
 

+ 25 - 24
appTools/ToolFilm.py

@@ -9,7 +9,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui
 
 
 from appTool import AppTool
 from appTool import AppTool
 from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
 from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
-    OptionalHideInputSection, FCComboBox, FCFileSaveDialog
+    OptionalHideInputSection, FCComboBox, FCFileSaveDialog, FCButton, FCLabel
 
 
 from copy import deepcopy
 from copy import deepcopy
 import logging
 import logging
@@ -759,7 +759,7 @@ class FilmUI:
         self.layout = layout
         self.layout = layout
 
 
         # ## Title
         # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label = FCLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
         title_label.setStyleSheet("""
                                 QLabel
                                 QLabel
                                 {
                                 {
@@ -768,7 +768,7 @@ class FilmUI:
                                 }
                                 }
                                 """)
                                 """)
         self.layout.addWidget(title_label)
         self.layout.addWidget(title_label)
-        self.layout.addWidget(QtWidgets.QLabel(""))
+        self.layout.addWidget(FCLabel(""))
 
 
         # Form Layout
         # Form Layout
         grid0 = QtWidgets.QGridLayout()
         grid0 = QtWidgets.QGridLayout()
@@ -781,7 +781,7 @@ class FilmUI:
         self.tf_type_obj_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
         self.tf_type_obj_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
                                            {'label': _('Geometry'), 'value': 'geo'}])
                                            {'label': _('Geometry'), 'value': 'geo'}])
 
 
-        self.tf_type_obj_combo_label = QtWidgets.QLabel('<b>%s</b>:' % _("Object"))
+        self.tf_type_obj_combo_label = FCLabel('<b>%s</b>:' % _("Object"))
         self.tf_type_obj_combo_label.setToolTip(
         self.tf_type_obj_combo_label.setToolTip(
             _("Specify the type of object for which to create the film.\n"
             _("Specify the type of object for which to create the film.\n"
               "The object can be of type: Gerber or Geometry.\n"
               "The object can be of type: Gerber or Geometry.\n"
@@ -804,7 +804,7 @@ class FilmUI:
         self.tf_type_box_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
         self.tf_type_box_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
                                            {'label': _('Geometry'), 'value': 'geo'}])
                                            {'label': _('Geometry'), 'value': 'geo'}])
 
 
-        self.tf_type_box_combo_label = QtWidgets.QLabel(_("Box Type:"))
+        self.tf_type_box_combo_label = FCLabel(_("Box Type:"))
         self.tf_type_box_combo_label.setToolTip(
         self.tf_type_box_combo_label.setToolTip(
             _("Specify the type of object to be used as an container for\n"
             _("Specify the type of object to be used as an container for\n"
               "film creation. It can be: Gerber or Geometry type."
               "film creation. It can be: Gerber or Geometry type."
@@ -827,7 +827,7 @@ class FilmUI:
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         grid0.addWidget(separator_line, 4, 0, 1, 2)
         grid0.addWidget(separator_line, 4, 0, 1, 2)
 
 
-        self.film_adj_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Adjustments"))
+        self.film_adj_label = FCLabel('<b>%s</b>' % _("Film Adjustments"))
         self.film_adj_label.setToolTip(
         self.film_adj_label.setToolTip(
             _("Sometime the printers will distort the print shape, especially the Laser types.\n"
             _("Sometime the printers will distort the print shape, especially the Laser types.\n"
               "This section provide the tools to compensate for the print distortions.")
               "This section provide the tools to compensate for the print distortions.")
@@ -848,7 +848,7 @@ class FilmUI:
         )
         )
         grid0.addWidget(self.film_scale_cb, 6, 0, 1, 2)
         grid0.addWidget(self.film_scale_cb, 6, 0, 1, 2)
 
 
-        self.film_scalex_label = QtWidgets.QLabel('%s:' % _("X factor"))
+        self.film_scalex_label = FCLabel('%s:' % _("X factor"))
         self.film_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_scalex_entry.set_range(-999.9999, 999.9999)
         self.film_scalex_entry.set_range(-999.9999, 999.9999)
         self.film_scalex_entry.set_precision(self.decimals)
         self.film_scalex_entry.set_precision(self.decimals)
@@ -857,7 +857,7 @@ class FilmUI:
         grid0.addWidget(self.film_scalex_label, 7, 0)
         grid0.addWidget(self.film_scalex_label, 7, 0)
         grid0.addWidget(self.film_scalex_entry, 7, 1)
         grid0.addWidget(self.film_scalex_entry, 7, 1)
 
 
-        self.film_scaley_label = QtWidgets.QLabel('%s:' % _("Y factor"))
+        self.film_scaley_label = FCLabel('%s:' % _("Y factor"))
         self.film_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_scaley_entry.set_range(-999.9999, 999.9999)
         self.film_scaley_entry.set_range(-999.9999, 999.9999)
         self.film_scaley_entry.set_precision(self.decimals)
         self.film_scaley_entry.set_precision(self.decimals)
@@ -892,7 +892,7 @@ class FilmUI:
         )
         )
         grid0.addWidget(self.film_skew_cb, 10, 0, 1, 2)
         grid0.addWidget(self.film_skew_cb, 10, 0, 1, 2)
 
 
-        self.film_skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
+        self.film_skewx_label = FCLabel('%s:' % _("X angle"))
         self.film_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_skewx_entry.set_range(-999.9999, 999.9999)
         self.film_skewx_entry.set_range(-999.9999, 999.9999)
         self.film_skewx_entry.set_precision(self.decimals)
         self.film_skewx_entry.set_precision(self.decimals)
@@ -901,7 +901,7 @@ class FilmUI:
         grid0.addWidget(self.film_skewx_label, 11, 0)
         grid0.addWidget(self.film_skewx_label, 11, 0)
         grid0.addWidget(self.film_skewx_entry, 11, 1)
         grid0.addWidget(self.film_skewx_entry, 11, 1)
 
 
-        self.film_skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
+        self.film_skewy_label = FCLabel('%s:' % _("Y angle"))
         self.film_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.film_skewy_entry.set_range(-999.9999, 999.9999)
         self.film_skewy_entry.set_range(-999.9999, 999.9999)
         self.film_skewy_entry.set_precision(self.decimals)
         self.film_skewy_entry.set_precision(self.decimals)
@@ -910,7 +910,7 @@ class FilmUI:
         grid0.addWidget(self.film_skewy_label, 12, 0)
         grid0.addWidget(self.film_skewy_label, 12, 0)
         grid0.addWidget(self.film_skewy_entry, 12, 1)
         grid0.addWidget(self.film_skewy_entry, 12, 1)
 
 
-        self.film_skew_ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
+        self.film_skew_ref_label = FCLabel('%s:' % _("Reference"))
         self.film_skew_ref_label.setToolTip(
         self.film_skew_ref_label.setToolTip(
             _("The reference point to be used as origin for the skew.\n"
             _("The reference point to be used as origin for the skew.\n"
               "It can be one of the four points of the geometry bounding box.")
               "It can be one of the four points of the geometry bounding box.")
@@ -957,7 +957,7 @@ class FilmUI:
                                           {'label': _('Y'), 'value': 'y'},
                                           {'label': _('Y'), 'value': 'y'},
                                           {'label': _('Both'), 'value': 'both'}],
                                           {'label': _('Both'), 'value': 'both'}],
                                          stretch=False)
                                          stretch=False)
-        self.film_mirror_axis_label = QtWidgets.QLabel('%s:' % _("Mirror axis"))
+        self.film_mirror_axis_label = FCLabel('%s:' % _("Mirror axis"))
 
 
         grid0.addWidget(self.film_mirror_axis_label, 16, 0)
         grid0.addWidget(self.film_mirror_axis_label, 16, 0)
         grid0.addWidget(self.film_mirror_axis, 16, 1)
         grid0.addWidget(self.film_mirror_axis, 16, 1)
@@ -973,7 +973,7 @@ class FilmUI:
         separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
         grid0.addWidget(separator_line2, 17, 0, 1, 2)
         grid0.addWidget(separator_line2, 17, 0, 1, 2)
 
 
-        self.film_param_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Parameters"))
+        self.film_param_label = FCLabel('<b>%s</b>' % _("Film Parameters"))
 
 
         grid0.addWidget(self.film_param_label, 18, 0, 1, 2)
         grid0.addWidget(self.film_param_label, 18, 0, 1, 2)
 
 
@@ -983,7 +983,7 @@ class FilmUI:
         self.film_scale_stroke_entry.setSingleStep(0.01)
         self.film_scale_stroke_entry.setSingleStep(0.01)
         self.film_scale_stroke_entry.set_precision(self.decimals)
         self.film_scale_stroke_entry.set_precision(self.decimals)
 
 
-        self.film_scale_stroke_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
+        self.film_scale_stroke_label = FCLabel('%s:' % _("Scale Stroke"))
         self.film_scale_stroke_label.setToolTip(
         self.film_scale_stroke_label.setToolTip(
             _("Scale the line stroke thickness of each feature in the SVG file.\n"
             _("Scale the line stroke thickness of each feature in the SVG file.\n"
               "It means that the line that envelope each SVG feature will be thicker or thinner,\n"
               "It means that the line that envelope each SVG feature will be thicker or thinner,\n"
@@ -996,7 +996,7 @@ class FilmUI:
         self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
         self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
                                    {'label': _('Negative'), 'value': 'neg'}],
                                    {'label': _('Negative'), 'value': 'neg'}],
                                   stretch=False)
                                   stretch=False)
-        self.film_type_label = QtWidgets.QLabel(_("Film Type:"))
+        self.film_type_label = FCLabel(_("Film Type:"))
         self.film_type_label.setToolTip(
         self.film_type_label.setToolTip(
             _("Generate a Positive black film or a Negative film.\n"
             _("Generate a Positive black film or a Negative film.\n"
               "Positive means that it will print the features\n"
               "Positive means that it will print the features\n"
@@ -1014,7 +1014,7 @@ class FilmUI:
         self.boundary_entry.setSingleStep(0.01)
         self.boundary_entry.setSingleStep(0.01)
         self.boundary_entry.set_precision(self.decimals)
         self.boundary_entry.set_precision(self.decimals)
 
 
-        self.boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
+        self.boundary_label = FCLabel('%s:' % _("Border"))
         self.boundary_label.setToolTip(
         self.boundary_label.setToolTip(
             _("Specify a border around the object.\n"
             _("Specify a border around the object.\n"
               "Only for negative film.\n"
               "Only for negative film.\n"
@@ -1051,7 +1051,7 @@ class FilmUI:
 
 
         self.ois_p = OptionalHideInputSection(self.punch_cb, [self.punch_frame])
         self.ois_p = OptionalHideInputSection(self.punch_cb, [self.punch_frame])
 
 
-        self.source_label = QtWidgets.QLabel('%s:' % _("Source"))
+        self.source_label = FCLabel('%s:' % _("Source"))
         self.source_label.setToolTip(
         self.source_label.setToolTip(
             _("The punch hole source can be:\n"
             _("The punch hole source can be:\n"
               "- Excellon -> an Excellon holes center will serve as reference.\n"
               "- Excellon -> an Excellon holes center will serve as reference.\n"
@@ -1063,7 +1063,7 @@ class FilmUI:
         punch_grid.addWidget(self.source_label, 0, 0)
         punch_grid.addWidget(self.source_label, 0, 0)
         punch_grid.addWidget(self.source_punch, 0, 1)
         punch_grid.addWidget(self.source_punch, 0, 1)
 
 
-        self.exc_label = QtWidgets.QLabel('%s:' % _("Excellon Obj"))
+        self.exc_label = FCLabel('%s:' % _("Excellon Obj"))
         self.exc_label.setToolTip(
         self.exc_label.setToolTip(
             _("Remove the geometry of Excellon from the Film to create the holes in pads.")
             _("Remove the geometry of Excellon from the Film to create the holes in pads.")
         )
         )
@@ -1079,7 +1079,7 @@ class FilmUI:
         self.exc_label.hide()
         self.exc_label.hide()
         self.exc_combo.hide()
         self.exc_combo.hide()
 
 
-        self.punch_size_label = QtWidgets.QLabel('%s:' % _("Punch Size"))
+        self.punch_size_label = FCLabel('%s:' % _("Punch Size"))
         self.punch_size_label.setToolTip(_("The value here will control how big is the punch hole in the pads."))
         self.punch_size_label.setToolTip(_("The value here will control how big is the punch hole in the pads."))
         self.punch_size_spinner = FCDoubleSpinner(callback=self.confirmation_message)
         self.punch_size_spinner = FCDoubleSpinner(callback=self.confirmation_message)
         self.punch_size_spinner.set_range(0, 999.9999)
         self.punch_size_spinner.set_range(0, 999.9999)
@@ -1108,7 +1108,7 @@ class FilmUI:
                                          {'label': _('PDF'), 'value': 'pdf'}
                                          {'label': _('PDF'), 'value': 'pdf'}
                                          ], stretch=False)
                                          ], stretch=False)
 
 
-        self.file_type_label = QtWidgets.QLabel(_("Film Type:"))
+        self.file_type_label = FCLabel(_("Film Type:"))
         self.file_type_label.setToolTip(
         self.file_type_label.setToolTip(
             _("The file type of the saved film. Can be:\n"
             _("The file type of the saved film. Can be:\n"
               "- 'SVG' -> open-source vectorial format\n"
               "- 'SVG' -> open-source vectorial format\n"
@@ -1119,7 +1119,7 @@ class FilmUI:
         grid1.addWidget(self.file_type_radio, 1, 1)
         grid1.addWidget(self.file_type_radio, 1, 1)
 
 
         # Page orientation
         # Page orientation
-        self.orientation_label = QtWidgets.QLabel('%s:' % _("Page Orientation"))
+        self.orientation_label = FCLabel('%s:' % _("Page Orientation"))
         self.orientation_label.setToolTip(_("Can be:\n"
         self.orientation_label.setToolTip(_("Can be:\n"
                                             "- Portrait\n"
                                             "- Portrait\n"
                                             "- Landscape"))
                                             "- Landscape"))
@@ -1132,7 +1132,7 @@ class FilmUI:
         grid1.addWidget(self.orientation_radio, 2, 1)
         grid1.addWidget(self.orientation_radio, 2, 1)
 
 
         # Page Size
         # Page Size
-        self.pagesize_label = QtWidgets.QLabel('%s:' % _("Page Size"))
+        self.pagesize_label = FCLabel('%s:' % _("Page Size"))
         self.pagesize_label.setToolTip(_("A selection of standard ISO 216 page sizes."))
         self.pagesize_label.setToolTip(_("A selection of standard ISO 216 page sizes."))
 
 
         self.pagesize_combo = FCComboBox()
         self.pagesize_combo = FCComboBox()
@@ -1200,7 +1200,8 @@ class FilmUI:
         self.on_film_type(val='hide')
         self.on_film_type(val='hide')
 
 
         # Buttons
         # Buttons
-        self.film_object_button = QtWidgets.QPushButton(_("Save Film"))
+        self.film_object_button = FCButton(_("Save Film"))
+        self.film_object_button.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png'))
         self.film_object_button.setToolTip(
         self.film_object_button.setToolTip(
             _("Create a Film for the selected object, within\n"
             _("Create a Film for the selected object, within\n"
               "the specified box. Does not create a new \n "
               "the specified box. Does not create a new \n "
@@ -1218,7 +1219,7 @@ class FilmUI:
         self.layout.addStretch()
         self.layout.addStretch()
 
 
         # ## Reset Tool
         # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button = FCButton(_("Reset Tool"))
         self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
         self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
         self.reset_button.setToolTip(
         self.reset_button.setToolTip(
             _("Will reset the tool parameters.")
             _("Will reset the tool parameters.")

+ 170 - 132
appTools/ToolImage.py

@@ -21,23 +21,165 @@ if '_' not in builtins.__dict__:
 
 
 class ToolImage(AppTool):
 class ToolImage(AppTool):
 
 
-    toolName = _("Image as Object")
-
     def __init__(self, app):
     def __init__(self, app):
         AppTool.__init__(self, app)
         AppTool.__init__(self, app)
 
 
         self.app = app
         self.app = app
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
-        # Title
-        title_label = QtWidgets.QLabel("%s" % _('Image to PCB'))
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = ImageUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
+
+        # ## Signals
+        self.ui.import_button.clicked.connect(self.on_file_importimage)
+        self.ui.image_type.activated_custom.connect(self.ui.on_image_type)
+
+    def run(self, toggle=True):
+        self.app.defaults.report_usage("ToolImage()")
+
+        if toggle:
+            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+            else:
+                try:
+                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
+                except AttributeError:
+                    pass
+        else:
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+
+        AppTool.run(self)
+        self.set_tool_ui()
+
+        self.app.ui.notebook.setTabText(2, _("Image Tool"))
+
+    def install(self, icon=None, separator=None, **kwargs):
+        AppTool.install(self, icon, separator, **kwargs)
+
+    def set_tool_ui(self):
+        # ## Initialize form
+        self.ui.dpi_entry.set_value(96)
+        self.ui.image_type.set_value('black')
+        self.ui.mask_bw_entry.set_value(250)
+        self.ui.mask_r_entry.set_value(250)
+        self.ui.mask_g_entry.set_value(250)
+        self.ui.mask_b_entry.set_value(250)
+
+    def on_file_importimage(self):
+        """
+        Callback for menu item File->Import IMAGE.
+        :param type_of_obj: to import the IMAGE as Geometry or as Gerber
+        :type type_of_obj: str
+        :return: None
+        """
+        mask = []
+        self.app.log.debug("on_file_importimage()")
+
+        _filter = "Image Files(*.BMP *.PNG *.JPG *.JPEG);;" \
+                  "Bitmap File (*.BMP);;" \
+                  "PNG File (*.PNG);;" \
+                  "Jpeg File (*.JPG);;" \
+                  "All Files (*.*)"
+        try:
+            filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import IMAGE"),
+                                                                 directory=self.app.get_last_folder(), filter=_filter)
+        except TypeError:
+            filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import IMAGE"), filter=filter)
+
+        filename = str(filename)
+        type_obj = self.ui.tf_type_obj_combo.get_value()
+        dpi = self.ui.dpi_entry.get_value()
+        mode = self.ui.image_type.get_value()
+        mask = [
+            self.ui.mask_bw_entry.get_value(),
+            self.ui.mask_r_entry.get_value(),
+            self.ui.mask_g_entry.get_value(),
+            self.ui.mask_b_entry.get_value()
+        ]
+
+        if filename == "":
+            self.app.inform.emit(_("Cancelled."))
+        else:
+            self.app.worker_task.emit({'fcn': self.import_image,
+                                       'params': [filename, type_obj, dpi, mode, mask]})
+
+    def import_image(self, filename, o_type=_("Gerber"), dpi=96, mode='black', mask=None, outname=None):
+        """
+        Adds a new Geometry Object to the projects and populates
+        it with shapes extracted from the SVG file.
+
+        :param filename: Path to the SVG file.
+        :param o_type: type of FlatCAM objeect
+        :param dpi: dot per inch
+        :param mode: black or color
+        :param mask: dictate the level of detail
+        :param outname: name for the resulting file
+        :return:
+        """
+
+        self.app.defaults.report_usage("import_image()")
+
+        if mask is None:
+            mask = [250, 250, 250, 250]
+
+        if o_type is None or o_type == _("Geometry"):
+            obj_type = "geometry"
+        elif o_type == _("Gerber"):
+            obj_type = "gerber"
+        else:
+            self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                 _("Not supported type is picked as parameter. "
+                                   "Only Geometry and Gerber are supported"))
+            return
+
+        def obj_init(geo_obj, app_obj):
+            geo_obj.import_image(filename, units=units, dpi=dpi, mode=mode, mask=mask)
+            geo_obj.multigeo = False
+
+        with self.app.proc_container.new(_("Importing Image")) as proc:
+
+            # Object name
+            name = outname or filename.split('/')[-1].split('\\')[-1]
+            units = self.app.defaults['units']
+
+            self.app.app_obj.new_object(obj_type, name, obj_init)
+
+            # Register recent file
+            self.app.file_opened.emit("image", filename)
+
+            # GUI feedback
+            self.app.inform.emit('[success] %s: %s' % (_("Opened"), filename))
+
+
+class ImageUI:
+
+    toolName = _("Image as Object")
+
+    def __init__(self, layout, app):
+        self.app = app
+        self.decimals = self.app.decimals
+        self.layout = layout
+
+        # ## Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
         title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
+                                QLabel
+                                {
+                                    font-size: 16px;
+                                    font-weight: bold;
+                                }
+                                """)
         self.layout.addWidget(title_label)
         self.layout.addWidget(title_label)
 
 
         # Form Layout
         # Form Layout
@@ -53,8 +195,8 @@ class ToolImage(AppTool):
 
 
         self.tf_type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.tf_type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.tf_type_obj_combo_label.setToolTip(
         self.tf_type_obj_combo_label.setToolTip(
-           _("Specify the type of object to create from the image.\n"
-             "It can be of type: Gerber or Geometry.")
+            _("Specify the type of object to create from the image.\n"
+              "It can be of type: Gerber or Geometry.")
 
 
         )
         )
         ti_form_layout.addRow(self.tf_type_obj_combo_label, self.tf_type_obj_combo)
         ti_form_layout.addRow(self.tf_type_obj_combo_label, self.tf_type_obj_combo)
@@ -63,7 +205,7 @@ class ToolImage(AppTool):
         self.dpi_entry = FCSpinner(callback=self.confirmation_message_int)
         self.dpi_entry = FCSpinner(callback=self.confirmation_message_int)
         self.dpi_entry.set_range(0, 99999)
         self.dpi_entry.set_range(0, 99999)
         self.dpi_label = QtWidgets.QLabel('%s:' % _("DPI value"))
         self.dpi_label = QtWidgets.QLabel('%s:' % _("DPI value"))
-        self.dpi_label.setToolTip(_("Specify a DPI value for the image.") )
+        self.dpi_label.setToolTip(_("Specify a DPI value for the image."))
         ti_form_layout.addRow(self.dpi_label, self.dpi_entry)
         ti_form_layout.addRow(self.dpi_label, self.dpi_entry)
 
 
         self.emty_lbl = QtWidgets.QLabel("")
         self.emty_lbl = QtWidgets.QLabel("")
@@ -150,48 +292,8 @@ class ToolImage(AppTool):
 
 
         self.on_image_type(val=False)
         self.on_image_type(val=False)
 
 
-        # ## Signals
-        self.import_button.clicked.connect(self.on_file_importimage)
-        self.image_type.activated_custom.connect(self.on_image_type)
-
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolImage()")
-
-        if toggle:
-            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
-            else:
-                try:
-                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        # if tab is populated with the tool but it does not have the focus, focus on it
-                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
-                            # focus on Tool Tab
-                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
-                        else:
-                            self.app.ui.splitter.setSizes([0, 1])
-                except AttributeError:
-                    pass
-        else:
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
-
-        AppTool.run(self)
-        self.set_tool_ui()
-
-        self.app.ui.notebook.setTabText(2, _("Image Tool"))
-
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, **kwargs)
-
-    def set_tool_ui(self):
-        # ## Initialize form
-        self.dpi_entry.set_value(96)
-        self.image_type.set_value('black')
-        self.mask_bw_entry.set_value(250)
-        self.mask_r_entry.set_value(250)
-        self.mask_g_entry.set_value(250)
-        self.mask_b_entry.set_value(250)
+        # #################################### FINSIHED GUI ###########################
+        # #############################################################################
 
 
     def on_image_type(self, val):
     def on_image_type(self, val):
         if val == 'color':
         if val == 'color':
@@ -215,83 +317,19 @@ class ToolImage(AppTool):
             self.mask_bw_label.setDisabled(False)
             self.mask_bw_label.setDisabled(False)
             self.mask_bw_entry.setDisabled(False)
             self.mask_bw_entry.setDisabled(False)
 
 
-    def on_file_importimage(self):
-        """
-        Callback for menu item File->Import IMAGE.
-        :param type_of_obj: to import the IMAGE as Geometry or as Gerber
-        :type type_of_obj: str
-        :return: None
-        """
-        mask = []
-        self.app.log.debug("on_file_importimage()")
-
-        _filter = "Image Files(*.BMP *.PNG *.JPG *.JPEG);;" \
-                  "Bitmap File (*.BMP);;" \
-                  "PNG File (*.PNG);;" \
-                  "Jpeg File (*.JPG);;" \
-                  "All Files (*.*)"
-        try:
-            filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import IMAGE"),
-                                                                 directory=self.app.get_last_folder(), filter=_filter)
-        except TypeError:
-            filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import IMAGE"), filter=filter)
-
-        filename = str(filename)
-        type_obj = self.tf_type_obj_combo.get_value()
-        dpi = self.dpi_entry.get_value()
-        mode = self.image_type.get_value()
-        mask = [self.mask_bw_entry.get_value(), self.mask_r_entry.get_value(), self.mask_g_entry.get_value(),
-                self.mask_b_entry.get_value()]
-
-        if filename == "":
-            self.app.inform.emit(_("Cancelled."))
+    def confirmation_message(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
+                                                                                  self.decimals,
+                                                                                  minval,
+                                                                                  self.decimals,
+                                                                                  maxval), False)
         else:
         else:
-            self.app.worker_task.emit({'fcn': self.import_image,
-                                       'params': [filename, type_obj, dpi, mode, mask]})
-
-    def import_image(self, filename, o_type=_("Gerber"), dpi=96, mode='black', mask=None, outname=None):
-        """
-        Adds a new Geometry Object to the projects and populates
-        it with shapes extracted from the SVG file.
-
-        :param filename: Path to the SVG file.
-        :param o_type: type of FlatCAM objeect
-        :param dpi: dot per inch
-        :param mode: black or color
-        :param mask: dictate the level of detail
-        :param outname: name for the resulting file
-        :return:
-        """
-
-        self.app.defaults.report_usage("import_image()")
-
-        if mask is None:
-            mask = [250, 250, 250, 250]
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
 
-        if o_type is None or o_type == _("Geometry"):
-            obj_type = "geometry"
-        elif o_type == _("Gerber"):
-            obj_type = "gerber"
+    def confirmation_message_int(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                            (_("Edited value is out of range"), minval, maxval), False)
         else:
         else:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("Not supported type is picked as parameter. "
-                                   "Only Geometry and Gerber are supported"))
-            return
-
-        def obj_init(geo_obj, app_obj):
-            geo_obj.import_image(filename, units=units, dpi=dpi, mode=mode, mask=mask)
-            geo_obj.multigeo = False
-
-        with self.app.proc_container.new(_("Importing Image")) as proc:
-
-            # Object name
-            name = outname or filename.split('/')[-1].split('\\')[-1]
-            units = self.app.defaults['units']
-
-            self.app.app_obj.new_object(obj_type, name, obj_init)
-
-            # Register recent file
-            self.app.file_opened.emit("image", filename)
-
-            # GUI feedback
-            self.app.inform.emit('[success] %s: %s' % (_("Opened"), filename))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)

+ 161 - 154
appTools/ToolInvertGerber.py

@@ -8,7 +8,7 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 from PyQt5 import QtWidgets, QtCore, QtGui
 
 
 from appTool import AppTool
 from appTool import AppTool
-from appGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox
+from appGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox, FCLabel
 
 
 from shapely.geometry import box
 from shapely.geometry import box
 
 
@@ -28,133 +28,20 @@ log = logging.getLogger('base')
 
 
 class ToolInvertGerber(AppTool):
 class ToolInvertGerber(AppTool):
 
 
-    toolName = _("Invert Gerber Tool")
-
     def __init__(self, app):
     def __init__(self, app):
         self.app = app
         self.app = app
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
         AppTool.__init__(self, app)
         AppTool.__init__(self, app)
 
 
-        self.tools_frame = QtWidgets.QFrame()
-        self.tools_frame.setContentsMargins(0, 0, 0, 0)
-        self.layout.addWidget(self.tools_frame)
-        self.tools_box = QtWidgets.QVBoxLayout()
-        self.tools_box.setContentsMargins(0, 0, 0, 0)
-        self.tools_frame.setLayout(self.tools_box)
-
-        # Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.tools_box.addWidget(title_label)
-
-        # Grid Layout
-        grid0 = QtWidgets.QGridLayout()
-        grid0.setColumnStretch(0, 0)
-        grid0.setColumnStretch(1, 1)
-        self.tools_box.addLayout(grid0)
-
-        grid0.addWidget(QtWidgets.QLabel(''), 0, 0, 1, 2)
-
-        # Target Gerber Object
-        self.gerber_combo = FCComboBox()
-        self.gerber_combo.setModel(self.app.collection)
-        self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.gerber_combo.is_last = True
-        self.gerber_combo.obj_type = "Gerber"
-
-        self.gerber_label = QtWidgets.QLabel('<b>%s:</b>' % _("GERBER"))
-        self.gerber_label.setToolTip(
-            _("Gerber object that will be inverted.")
-        )
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = InvertUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
 
 
-        grid0.addWidget(self.gerber_label, 1, 0, 1, 2)
-        grid0.addWidget(self.gerber_combo, 2, 0, 1, 2)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 3, 0, 1, 2)
-
-        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.param_label.setToolTip('%s.' % _("Parameters for this tool"))
-
-        grid0.addWidget(self.param_label, 4, 0, 1, 2)
-
-        # Margin
-        self.margin_label = QtWidgets.QLabel('%s:' % _('Margin'))
-        self.margin_label.setToolTip(
-            _("Distance by which to avoid\n"
-              "the edges of the Gerber object.")
-        )
-        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.margin_entry.set_precision(self.decimals)
-        self.margin_entry.set_range(0.0000, 9999.9999)
-        self.margin_entry.setObjectName(_("Margin"))
-
-        grid0.addWidget(self.margin_label, 5, 0, 1, 2)
-        grid0.addWidget(self.margin_entry, 6, 0, 1, 2)
-
-        self.join_label = QtWidgets.QLabel('%s:' % _("Lines Join Style"))
-        self.join_label.setToolTip(
-            _("The way that the lines in the object outline will be joined.\n"
-              "Can be:\n"
-              "- rounded -> an arc is added between two joining lines\n"
-              "- square -> the lines meet in 90 degrees angle\n"
-              "- bevel -> the lines are joined by a third line")
-        )
-        self.join_radio = RadioSet([
-            {'label': 'Rounded', 'value': 'r'},
-            {'label': 'Square', 'value': 's'},
-            {'label': 'Bevel', 'value': 'b'}
-        ], orientation='vertical', stretch=False)
-
-        grid0.addWidget(self.join_label, 7, 0, 1, 2)
-        grid0.addWidget(self.join_radio, 8, 0, 1, 2)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 9, 0, 1, 2)
-
-        self.invert_btn = FCButton(_('Invert Gerber'))
-        self.invert_btn.setToolTip(
-            _("Will invert the Gerber object: areas that have copper\n"
-              "will be empty of copper and previous empty area will be\n"
-              "filled with copper.")
-        )
-        self.invert_btn.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid0.addWidget(self.invert_btn, 10, 0, 1, 2)
-
-        self.tools_box.addStretch()
-
-        # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
-        self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
-        self.reset_button.setToolTip(
-            _("Will reset the tool parameters.")
-        )
-        self.reset_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.tools_box.addWidget(self.reset_button)
-
-        self.invert_btn.clicked.connect(self.on_grb_invert)
-        self.reset_button.clicked.connect(self.set_tool_ui)
+        self.ui.invert_btn.clicked.connect(self.on_grb_invert)
+        self.ui.reset_button.clicked.connect(self.set_tool_ui)
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):
         AppTool.install(self, icon, separator, shortcut='', **kwargs)
         AppTool.install(self, icon, separator, shortcut='', **kwargs)
@@ -188,20 +75,20 @@ class ToolInvertGerber(AppTool):
         self.app.ui.notebook.setTabText(2, _("Invert Tool"))
         self.app.ui.notebook.setTabText(2, _("Invert Tool"))
 
 
     def set_tool_ui(self):
     def set_tool_ui(self):
-        self.margin_entry.set_value(float(self.app.defaults["tools_invert_margin"]))
-        self.join_radio.set_value(self.app.defaults["tools_invert_join_style"])
+        self.ui.margin_entry.set_value(float(self.app.defaults["tools_invert_margin"]))
+        self.ui.join_radio.set_value(self.app.defaults["tools_invert_join_style"])
 
 
     def on_grb_invert(self):
     def on_grb_invert(self):
-        margin = self.margin_entry.get_value()
+        margin = self.ui.margin_entry.get_value()
         if round(margin, self.decimals) == 0.0:
         if round(margin, self.decimals) == 0.0:
             margin = 1E-10
             margin = 1E-10
 
 
-        join_style = {'r': 1, 'b': 3, 's': 2}[self.join_radio.get_value()]
+        join_style = {'r': 1, 'b': 3, 's': 2}[self.ui.join_radio.get_value()]
         if join_style is None:
         if join_style is None:
             join_style = 'r'
             join_style = 'r'
 
 
         grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
         grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
-        obj_name = self.gerber_combo.currentText()
+        obj_name = self.ui.gerber_combo.currentText()
 
 
         outname = obj_name + "_inverted"
         outname = obj_name + "_inverted"
 
 
@@ -243,32 +130,6 @@ class ToolInvertGerber(AppTool):
 
 
         new_apertures = {}
         new_apertures = {}
 
 
-        # for apid, val in grb_obj.apertures.items():
-        #     new_apertures[apid] = {}
-        #     for key in val:
-        #         if key == 'geometry':
-        #             new_apertures[apid]['geometry'] = []
-        #             for elem in val['geometry']:
-        #                 geo_elem = {}
-        #                 if 'follow' in elem:
-        #                     try:
-        #                         geo_elem['clear'] = elem['follow'].buffer(val['size'] / 2.0).exterior
-        #                     except AttributeError:
-        #                         # TODO should test if width or height is bigger
-        #                         geo_elem['clear'] = elem['follow'].buffer(val['width'] / 2.0).exterior
-        #                 if 'clear' in elem:
-        #                     if isinstance(elem['clear'], Polygon):
-        #                         try:
-        #                             geo_elem['solid'] = elem['clear'].buffer(val['size'] / 2.0, grb_circle_steps)
-        #                         except AttributeError:
-        #                             # TODO should test if width or height is bigger
-        #                             geo_elem['solid'] = elem['clear'].buffer(val['width'] / 2.0, grb_circle_steps)
-        #                     else:
-        #                         geo_elem['follow'] = elem['clear']
-        #                 new_apertures[apid]['geometry'].append(deepcopy(geo_elem))
-        #         else:
-        #             new_apertures[apid][key] = deepcopy(val[key])
-
         if '0' not in new_apertures:
         if '0' not in new_apertures:
             new_apertures['0'] = {}
             new_apertures['0'] = {}
             new_apertures['0']['type'] = 'C'
             new_apertures['0']['type'] = 'C'
@@ -302,9 +163,155 @@ class ToolInvertGerber(AppTool):
         self.app.app_obj.new_object('gerber', outname, init_func)
         self.app.app_obj.new_object('gerber', outname, init_func)
 
 
     def reset_fields(self):
     def reset_fields(self):
-        self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.ui.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
 
 
     @staticmethod
     @staticmethod
     def poly2rings(poly):
     def poly2rings(poly):
         return [poly.exterior] + [interior for interior in poly.interiors]
         return [poly.exterior] + [interior for interior in poly.interiors]
-# end of file
+
+
+class InvertUI:
+    
+    toolName = _("Invert Gerber Tool")
+
+    def __init__(self, layout, app):
+        self.app = app
+        self.decimals = self.app.decimals
+        self.layout = layout
+
+        # ## Title
+        title_label = FCLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                                QLabel
+                                {
+                                    font-size: 16px;
+                                    font-weight: bold;
+                                }
+                                """)
+        self.layout.addWidget(title_label)
+        self.layout.addWidget(FCLabel(""))
+
+        self.tools_frame = QtWidgets.QFrame()
+        self.tools_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.tools_frame)
+
+        self.tools_box = QtWidgets.QVBoxLayout()
+        self.tools_box.setContentsMargins(0, 0, 0, 0)
+        self.tools_frame.setLayout(self.tools_box)
+
+        # Grid Layout
+        grid0 = QtWidgets.QGridLayout()
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
+        self.tools_box.addLayout(grid0)
+
+        # Target Gerber Object
+        self.gerber_combo = FCComboBox()
+        self.gerber_combo.setModel(self.app.collection)
+        self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.gerber_combo.is_last = True
+        self.gerber_combo.obj_type = "Gerber"
+
+        self.gerber_label = FCLabel('<b>%s:</b>' % _("GERBER"))
+        self.gerber_label.setToolTip(
+            _("Gerber object that will be inverted.")
+        )
+
+        grid0.addWidget(self.gerber_label, 1, 0, 1, 2)
+        grid0.addWidget(self.gerber_combo, 2, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 3, 0, 1, 2)
+
+        self.param_label = FCLabel("<b>%s:</b>" % _("Parameters"))
+        self.param_label.setToolTip('%s.' % _("Parameters for this tool"))
+
+        grid0.addWidget(self.param_label, 4, 0, 1, 2)
+
+        # Margin
+        self.margin_label = FCLabel('%s:' % _('Margin'))
+        self.margin_label.setToolTip(
+            _("Distance by which to avoid\n"
+              "the edges of the Gerber object.")
+        )
+        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.margin_entry.set_precision(self.decimals)
+        self.margin_entry.set_range(0.0000, 9999.9999)
+        self.margin_entry.setObjectName(_("Margin"))
+
+        grid0.addWidget(self.margin_label, 5, 0, 1, 2)
+        grid0.addWidget(self.margin_entry, 6, 0, 1, 2)
+
+        self.join_label = FCLabel('%s:' % _("Lines Join Style"))
+        self.join_label.setToolTip(
+            _("The way that the lines in the object outline will be joined.\n"
+              "Can be:\n"
+              "- rounded -> an arc is added between two joining lines\n"
+              "- square -> the lines meet in 90 degrees angle\n"
+              "- bevel -> the lines are joined by a third line")
+        )
+        self.join_radio = RadioSet([
+            {'label': 'Rounded', 'value': 'r'},
+            {'label': 'Square', 'value': 's'},
+            {'label': 'Bevel', 'value': 'b'}
+        ], orientation='vertical', stretch=False)
+
+        grid0.addWidget(self.join_label, 7, 0, 1, 2)
+        grid0.addWidget(self.join_radio, 8, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 9, 0, 1, 2)
+
+        self.invert_btn = FCButton(_('Invert Gerber'))
+        self.invert_btn.setToolTip(
+            _("Will invert the Gerber object: areas that have copper\n"
+              "will be empty of copper and previous empty area will be\n"
+              "filled with copper.")
+        )
+        self.invert_btn.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        grid0.addWidget(self.invert_btn, 10, 0, 1, 2)
+
+        self.tools_box.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = FCButton(_("Reset Tool"))
+        self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        self.tools_box.addWidget(self.reset_button)
+
+        # #################################### FINSIHED GUI ###########################
+        # #############################################################################
+
+    def confirmation_message(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
+                                                                                  self.decimals,
+                                                                                  minval,
+                                                                                  self.decimals,
+                                                                                  maxval), False)
+        else:
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+
+    def confirmation_message_int(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                            (_("Edited value is out of range"), minval, maxval), False)
+        else:
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)

+ 283 - 255
appTools/ToolOptimal.py

@@ -30,8 +30,6 @@ log = logging.getLogger('base')
 
 
 class ToolOptimal(AppTool):
 class ToolOptimal(AppTool):
 
 
-    toolName = _("Optimal Tool")
-
     update_text = QtCore.pyqtSignal(list)
     update_text = QtCore.pyqtSignal(list)
     update_sec_distances = QtCore.pyqtSignal(dict)
     update_sec_distances = QtCore.pyqtSignal(dict)
 
 
@@ -41,222 +39,11 @@ class ToolOptimal(AppTool):
         self.units = self.app.defaults['units'].upper()
         self.units = self.app.defaults['units'].upper()
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
-        # ############################################################################
-        # ############################ GUI creation ##################################
-        # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet(
-            """
-            QLabel
-            {
-                font-size: 16px;
-                font-weight: bold;
-            }
-            """)
-        self.layout.addWidget(title_label)
-
-        # ## Form Layout
-        form_lay = QtWidgets.QFormLayout()
-        self.layout.addLayout(form_lay)
-
-        form_lay.addRow(QtWidgets.QLabel(""))
-
-        # ## Gerber Object to mirror
-        self.gerber_object_combo = FCComboBox()
-        self.gerber_object_combo.setModel(self.app.collection)
-        self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.gerber_object_combo.is_last = True
-        self.gerber_object_combo.obj_type = "Gerber"
-
-        self.gerber_object_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
-        self.gerber_object_label.setToolTip(
-            "Gerber object for which to find the minimum distance between copper features."
-        )
-        form_lay.addRow(self.gerber_object_label)
-        form_lay.addRow(self.gerber_object_combo)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        form_lay.addRow(separator_line)
-
-        # Precision = nr of decimals
-        self.precision_label = QtWidgets.QLabel('%s:' % _("Precision"))
-        self.precision_label.setToolTip(_("Number of decimals kept for found distances."))
-
-        self.precision_spinner = FCSpinner(callback=self.confirmation_message_int)
-        self.precision_spinner.set_range(2, 10)
-        self.precision_spinner.setWrapping(True)
-        form_lay.addRow(self.precision_label, self.precision_spinner)
-
-        # Results Title
-        self.title_res_label = QtWidgets.QLabel('<b>%s:</b>' % _("Minimum distance"))
-        self.title_res_label.setToolTip(_("Display minimum distance between copper features."))
-        form_lay.addRow(self.title_res_label)
-
-        # Result value
-        self.result_label = QtWidgets.QLabel('%s:' % _("Determined"))
-        self.result_entry = FCEntry()
-        self.result_entry.setReadOnly(True)
-
-        self.units_lbl = QtWidgets.QLabel(self.units.lower())
-        self.units_lbl.setDisabled(True)
-
-        hlay = QtWidgets.QHBoxLayout()
-        hlay.addWidget(self.result_entry)
-        hlay.addWidget(self.units_lbl)
-
-        form_lay.addRow(self.result_label, hlay)
-
-        # Frequency of minimum encounter
-        self.freq_label = QtWidgets.QLabel('%s:' % _("Occurring"))
-        self.freq_label.setToolTip(_("How many times this minimum is found."))
-        self.freq_entry = FCEntry()
-        self.freq_entry.setReadOnly(True)
-        form_lay.addRow(self.freq_label, self.freq_entry)
-
-        # Control if to display the locations of where the minimum was found
-        self.locations_cb = FCCheckBox(_("Minimum points coordinates"))
-        self.locations_cb.setToolTip(_("Coordinates for points where minimum distance was found."))
-        form_lay.addRow(self.locations_cb)
-
-        # Locations where minimum was found
-        self.locations_textb = FCTextArea(parent=self)
-        self.locations_textb.setPlaceholderText(
-            _("Coordinates for points where minimum distance was found.")
-        )
-        self.locations_textb.setReadOnly(True)
-        stylesheet = """
-                        QTextEdit { selection-background-color:blue;
-                                    selection-color:white;
-                        }
-                     """
-
-        self.locations_textb.setStyleSheet(stylesheet)
-        form_lay.addRow(self.locations_textb)
-
-        # Jump button
-        self.locate_button = QtWidgets.QPushButton(_("Jump to selected position"))
-        self.locate_button.setToolTip(
-            _("Select a position in the Locations text box and then\n"
-              "click this button.")
-        )
-        self.locate_button.setMinimumWidth(60)
-        self.locate_button.setDisabled(True)
-        form_lay.addRow(self.locate_button)
-
-        # Other distances in Gerber
-        self.title_second_res_label = QtWidgets.QLabel('<b>%s:</b>' % _("Other distances"))
-        self.title_second_res_label.setToolTip(_("Will display other distances in the Gerber file ordered from\n"
-                                                 "the minimum to the maximum, not including the absolute minimum."))
-        form_lay.addRow(self.title_second_res_label)
-
-        # Control if to display the locations of where the minimum was found
-        self.sec_locations_cb = FCCheckBox(_("Other distances points coordinates"))
-        self.sec_locations_cb.setToolTip(_("Other distances and the coordinates for points\n"
-                                           "where the distance was found."))
-        form_lay.addRow(self.sec_locations_cb)
-
-        # this way I can hide/show the frame
-        self.sec_locations_frame = QtWidgets.QFrame()
-        self.sec_locations_frame.setContentsMargins(0, 0, 0, 0)
-        self.layout.addWidget(self.sec_locations_frame)
-        self.distances_box = QtWidgets.QVBoxLayout()
-        self.distances_box.setContentsMargins(0, 0, 0, 0)
-        self.sec_locations_frame.setLayout(self.distances_box)
-
-        # Other Distances label
-        self.distances_label = QtWidgets.QLabel('%s' % _("Gerber distances"))
-        self.distances_label.setToolTip(_("Other distances and the coordinates for points\n"
-                                          "where the distance was found."))
-        self.distances_box.addWidget(self.distances_label)
-
-        # Other distances
-        self.distances_textb = FCTextArea(parent=self)
-        self.distances_textb.setPlaceholderText(
-            _("Other distances and the coordinates for points\n"
-              "where the distance was found.")
-        )
-        self.distances_textb.setReadOnly(True)
-        stylesheet = """
-                        QTextEdit { selection-background-color:blue;
-                                    selection-color:white;
-                        }
-                     """
-
-        self.distances_textb.setStyleSheet(stylesheet)
-        self.distances_box.addWidget(self.distances_textb)
-
-        self.distances_box.addWidget(QtWidgets.QLabel(''))
-
-        # Other Locations label
-        self.locations_label = QtWidgets.QLabel('%s' % _("Points coordinates"))
-        self.locations_label.setToolTip(_("Other distances and the coordinates for points\n"
-                                          "where the distance was found."))
-        self.distances_box.addWidget(self.locations_label)
-
-        # Locations where minimum was found
-        self.locations_sec_textb = FCTextArea(parent=self)
-        self.locations_sec_textb.setPlaceholderText(
-            _("Other distances and the coordinates for points\n"
-              "where the distance was found.")
-        )
-        self.locations_sec_textb.setReadOnly(True)
-        stylesheet = """
-                        QTextEdit { selection-background-color:blue;
-                                    selection-color:white;
-                        }
-                     """
-
-        self.locations_sec_textb.setStyleSheet(stylesheet)
-        self.distances_box.addWidget(self.locations_sec_textb)
-
-        # Jump button
-        self.locate_sec_button = QtWidgets.QPushButton(_("Jump to selected position"))
-        self.locate_sec_button.setToolTip(
-            _("Select a position in the Locations text box and then\n"
-              "click this button.")
-        )
-        self.locate_sec_button.setMinimumWidth(60)
-        self.locate_sec_button.setDisabled(True)
-        self.distances_box.addWidget(self.locate_sec_button)
-
-        # GO button
-        self.calculate_button = QtWidgets.QPushButton(_("Find Minimum"))
-        self.calculate_button.setToolTip(
-            _("Calculate the minimum distance between copper features,\n"
-              "this will allow the determination of the right tool to\n"
-              "use for isolation or copper clearing.")
-        )
-        self.calculate_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.calculate_button.setMinimumWidth(60)
-        self.layout.addWidget(self.calculate_button)
-
-        self.layout.addStretch()
-
-        # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
-        self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
-        self.reset_button.setToolTip(
-            _("Will reset the tool parameters.")
-        )
-        self.reset_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(self.reset_button)
-
-        self.loc_ois = OptionalHideInputSection(self.locations_cb, [self.locations_textb, self.locate_button])
-        self.sec_loc_ois = OptionalHideInputSection(self.sec_locations_cb, [self.sec_locations_frame])
-        # ################## Finished GUI creation ###################################
-        # ############################################################################
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = OptimalUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
 
 
         # this is the line selected in the textbox with the locations of the minimum
         # this is the line selected in the textbox with the locations of the minimum
         self.selected_text = ''
         self.selected_text = ''
@@ -271,17 +58,17 @@ class ToolOptimal(AppTool):
         # ############################################################################
         # ############################################################################
         # ############################ Signals #######################################
         # ############################ Signals #######################################
         # ############################################################################
         # ############################################################################
-        self.calculate_button.clicked.connect(self.find_minimum_distance)
-        self.locate_button.clicked.connect(self.on_locate_position)
-        self.update_text.connect(self.on_update_text)
-        self.locations_textb.cursorPositionChanged.connect(self.on_textbox_clicked)
+        self.ui.calculate_button.clicked.connect(self.find_minimum_distance)
+        self.ui.locate_button.clicked.connect(self.on_locate_position)
+        self.ui.update_text.connect(self.on_update_text)
+        self.ui.locations_textb.cursorPositionChanged.connect(self.on_textbox_clicked)
 
 
-        self.locate_sec_button.clicked.connect(self.on_locate_sec_position)
-        self.update_sec_distances.connect(self.on_update_sec_distances_txt)
-        self.distances_textb.cursorPositionChanged.connect(self.on_distances_textb_clicked)
-        self.locations_sec_textb.cursorPositionChanged.connect(self.on_locations_sec_clicked)
+        self.ui.locate_sec_button.clicked.connect(self.on_locate_sec_position)
+        self.ui.update_sec_distances.connect(self.on_update_sec_distances_txt)
+        self.ui.distances_textb.cursorPositionChanged.connect(self.on_distances_textb_clicked)
+        self.ui.locations_sec_textb.cursorPositionChanged.connect(self.on_locations_sec_clicked)
 
 
-        self.reset_button.clicked.connect(self.set_tool_ui)
+        self.ui.reset_button.clicked.connect(self.set_tool_ui)
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):
         AppTool.install(self, icon, separator, shortcut='Alt+O', **kwargs)
         AppTool.install(self, icon, separator, shortcut='Alt+O', **kwargs)
@@ -314,13 +101,13 @@ class ToolOptimal(AppTool):
         self.app.ui.notebook.setTabText(2, _("Optimal Tool"))
         self.app.ui.notebook.setTabText(2, _("Optimal Tool"))
 
 
     def set_tool_ui(self):
     def set_tool_ui(self):
-        self.result_entry.set_value(0.0)
-        self.freq_entry.set_value('0')
+        self.ui.result_entry.set_value(0.0)
+        self.ui.freq_entry.set_value('0')
 
 
-        self.precision_spinner.set_value(int(self.app.defaults["tools_opt_precision"]))
-        self.locations_textb.clear()
+        self.ui.precision_spinner.set_value(int(self.app.defaults["tools_opt_precision"]))
+        self.ui.locations_textb.clear()
         # new cursor - select all document
         # new cursor - select all document
-        cursor = self.locations_textb.textCursor()
+        cursor = self.ui.locations_textb.textCursor()
         cursor.select(QtGui.QTextCursor.Document)
         cursor.select(QtGui.QTextCursor.Document)
 
 
         # clear previous selection highlight
         # clear previous selection highlight
@@ -328,20 +115,20 @@ class ToolOptimal(AppTool):
         tmp.clearBackground()
         tmp.clearBackground()
         cursor.setBlockFormat(tmp)
         cursor.setBlockFormat(tmp)
 
 
-        self.locations_textb.setVisible(False)
-        self.locate_button.setVisible(False)
+        self.ui.locations_textb.setVisible(False)
+        self.ui.locate_button.setVisible(False)
 
 
-        self.result_entry.set_value(0.0)
-        self.freq_entry.set_value('0')
+        self.ui.result_entry.set_value(0.0)
+        self.ui.freq_entry.set_value('0')
         self.reset_fields()
         self.reset_fields()
 
 
     def find_minimum_distance(self):
     def find_minimum_distance(self):
         self.units = self.app.defaults['units'].upper()
         self.units = self.app.defaults['units'].upper()
-        self.decimals = int(self.precision_spinner.get_value())
+        self.decimals = int(self.ui.precision_spinner.get_value())
 
 
-        selection_index = self.gerber_object_combo.currentIndex()
+        selection_index = self.ui.gerber_object_combo.currentIndex()
 
 
-        model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
+        model_index = self.app.collection.index(selection_index, 0, self.ui.gerber_object_combo.rootModelIndex())
         try:
         try:
             fcobj = model_index.internalPointer().obj
             fcobj = model_index.internalPointer().obj
         except Exception as e:
         except Exception as e:
@@ -426,17 +213,16 @@ class ToolOptimal(AppTool):
                             old_disp_number = disp_number
                             old_disp_number = disp_number
                     idx += 1
                     idx += 1
 
 
-                app_obj.inform.emit(
-                    _("Optimal Tool. Finding the minimum distance."))
+                app_obj.inform.emit(_("Optimal Tool. Finding the minimum distance."))
 
 
                 min_list = list(self.min_dict.keys())
                 min_list = list(self.min_dict.keys())
                 min_dist = min(min_list)
                 min_dist = min(min_list)
                 min_dist_string = '%.*f' % (self.decimals, float(min_dist))
                 min_dist_string = '%.*f' % (self.decimals, float(min_dist))
-                self.result_entry.set_value(min_dist_string)
+                self.ui.result_entry.set_value(min_dist_string)
 
 
                 freq = len(self.min_dict[min_dist])
                 freq = len(self.min_dict[min_dist])
                 freq = '%d' % int(freq)
                 freq = '%d' % int(freq)
-                self.freq_entry.set_value(freq)
+                self.ui.freq_entry.set_value(freq)
 
 
                 min_locations = self.min_dict.pop(min_dist)
                 min_locations = self.min_dict.pop(min_dist)
 
 
@@ -484,12 +270,12 @@ class ToolOptimal(AppTool):
         for loc in data:
         for loc in data:
             if loc:
             if loc:
                 txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
                 txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
-        self.locations_textb.setPlainText(txt)
-        self.locate_button.setDisabled(False)
+        self.ui.locations_textb.setPlainText(txt)
+        self.ui.locate_button.setDisabled(False)
 
 
     def on_textbox_clicked(self):
     def on_textbox_clicked(self):
         # new cursor - select all document
         # new cursor - select all document
-        cursor = self.locations_textb.textCursor()
+        cursor = self.ui.locations_textb.textCursor()
         cursor.select(QtGui.QTextCursor.Document)
         cursor.select(QtGui.QTextCursor.Document)
 
 
         # clear previous selection highlight
         # clear previous selection highlight
@@ -498,7 +284,7 @@ class ToolOptimal(AppTool):
         cursor.setBlockFormat(tmp)
         cursor.setBlockFormat(tmp)
 
 
         # new cursor - select the current line
         # new cursor - select the current line
-        cursor = self.locations_textb.textCursor()
+        cursor = self.ui.locations_textb.textCursor()
         cursor.select(QtGui.QTextCursor.LineUnderCursor)
         cursor.select(QtGui.QTextCursor.LineUnderCursor)
 
 
         # highlight the current selected line
         # highlight the current selected line
@@ -513,12 +299,12 @@ class ToolOptimal(AppTool):
         txt = ''
         txt = ''
         for loc in distance_list:
         for loc in distance_list:
             txt += '%s\n' % str(loc)
             txt += '%s\n' % str(loc)
-        self.distances_textb.setPlainText(txt)
-        self.locate_sec_button.setDisabled(False)
+        self.ui.distances_textb.setPlainText(txt)
+        self.ui.locate_sec_button.setDisabled(False)
 
 
     def on_distances_textb_clicked(self):
     def on_distances_textb_clicked(self):
         # new cursor - select all document
         # new cursor - select all document
-        cursor = self.distances_textb.textCursor()
+        cursor = self.ui.distances_textb.textCursor()
         cursor.select(QtGui.QTextCursor.Document)
         cursor.select(QtGui.QTextCursor.Document)
 
 
         # clear previous selection highlight
         # clear previous selection highlight
@@ -527,7 +313,7 @@ class ToolOptimal(AppTool):
         cursor.setBlockFormat(tmp)
         cursor.setBlockFormat(tmp)
 
 
         # new cursor - select the current line
         # new cursor - select the current line
-        cursor = self.distances_textb.textCursor()
+        cursor = self.ui.distances_textb.textCursor()
         cursor.select(QtGui.QTextCursor.LineUnderCursor)
         cursor.select(QtGui.QTextCursor.LineUnderCursor)
 
 
         # highlight the current selected line
         # highlight the current selected line
@@ -545,11 +331,11 @@ class ToolOptimal(AppTool):
         for loc in distance_list:
         for loc in distance_list:
             if loc:
             if loc:
                 txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
                 txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
-        self.locations_sec_textb.setPlainText(txt)
+        self.ui.locations_sec_textb.setPlainText(txt)
 
 
     def on_locations_sec_clicked(self):
     def on_locations_sec_clicked(self):
         # new cursor - select all document
         # new cursor - select all document
-        cursor = self.locations_sec_textb.textCursor()
+        cursor = self.ui.locations_sec_textb.textCursor()
         cursor.select(QtGui.QTextCursor.Document)
         cursor.select(QtGui.QTextCursor.Document)
 
 
         # clear previous selection highlight
         # clear previous selection highlight
@@ -558,7 +344,7 @@ class ToolOptimal(AppTool):
         cursor.setBlockFormat(tmp)
         cursor.setBlockFormat(tmp)
 
 
         # new cursor - select the current line
         # new cursor - select the current line
-        cursor = self.locations_sec_textb.textCursor()
+        cursor = self.ui.locations_sec_textb.textCursor()
         cursor.select(QtGui.QTextCursor.LineUnderCursor)
         cursor.select(QtGui.QTextCursor.LineUnderCursor)
 
 
         # highlight the current selected line
         # highlight the current selected line
@@ -593,5 +379,247 @@ class ToolOptimal(AppTool):
             return
             return
 
 
     def reset_fields(self):
     def reset_fields(self):
+        self.ui.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.ui.gerber_object_combo.setCurrentIndex(0)
+
+
+class OptimalUI:
+
+    toolName = _("Optimal Tool")
+
+    def __init__(self, layout, app):
+        self.app = app
+        self.decimals = self.app.decimals
+        self.layout = layout
+        self.units = self.app.defaults['units'].upper()
+
+        # ## Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                                QLabel
+                                {
+                                    font-size: 16px;
+                                    font-weight: bold;
+                                }
+                                """)
+        self.layout.addWidget(title_label)
+        self.layout.addWidget(QtWidgets.QLabel(""))
+
+        # ## Form Layout
+        form_lay = QtWidgets.QFormLayout()
+        self.layout.addLayout(form_lay)
+
+        # ## Gerber Object to mirror
+        self.gerber_object_combo = FCComboBox()
+        self.gerber_object_combo.setModel(self.app.collection)
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.gerber_object_combo.setCurrentIndex(0)
+        self.gerber_object_combo.is_last = True
+        self.gerber_object_combo.obj_type = "Gerber"
+
+        self.gerber_object_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
+        self.gerber_object_label.setToolTip(
+            "Gerber object for which to find the minimum distance between copper features."
+        )
+        form_lay.addRow(self.gerber_object_label)
+        form_lay.addRow(self.gerber_object_combo)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        form_lay.addRow(separator_line)
+
+        # Precision = nr of decimals
+        self.precision_label = QtWidgets.QLabel('%s:' % _("Precision"))
+        self.precision_label.setToolTip(_("Number of decimals kept for found distances."))
+
+        self.precision_spinner = FCSpinner(callback=self.confirmation_message_int)
+        self.precision_spinner.set_range(2, 10)
+        self.precision_spinner.setWrapping(True)
+        form_lay.addRow(self.precision_label, self.precision_spinner)
+
+        # Results Title
+        self.title_res_label = QtWidgets.QLabel('<b>%s:</b>' % _("Minimum distance"))
+        self.title_res_label.setToolTip(_("Display minimum distance between copper features."))
+        form_lay.addRow(self.title_res_label)
+
+        # Result value
+        self.result_label = QtWidgets.QLabel('%s:' % _("Determined"))
+        self.result_entry = FCEntry()
+        self.result_entry.setReadOnly(True)
+
+        self.units_lbl = QtWidgets.QLabel(self.units.lower())
+        self.units_lbl.setDisabled(True)
+
+        hlay = QtWidgets.QHBoxLayout()
+        hlay.addWidget(self.result_entry)
+        hlay.addWidget(self.units_lbl)
+
+        form_lay.addRow(self.result_label, hlay)
+
+        # Frequency of minimum encounter
+        self.freq_label = QtWidgets.QLabel('%s:' % _("Occurring"))
+        self.freq_label.setToolTip(_("How many times this minimum is found."))
+        self.freq_entry = FCEntry()
+        self.freq_entry.setReadOnly(True)
+        form_lay.addRow(self.freq_label, self.freq_entry)
+
+        # Control if to display the locations of where the minimum was found
+        self.locations_cb = FCCheckBox(_("Minimum points coordinates"))
+        self.locations_cb.setToolTip(_("Coordinates for points where minimum distance was found."))
+        form_lay.addRow(self.locations_cb)
+
+        # Locations where minimum was found
+        self.locations_textb = FCTextArea(parent=self)
+        self.locations_textb.setPlaceholderText(
+            _("Coordinates for points where minimum distance was found.")
+        )
+        self.locations_textb.setReadOnly(True)
+        stylesheet = """
+                                QTextEdit { selection-background-color:blue;
+                                            selection-color:white;
+                                }
+                             """
+
+        self.locations_textb.setStyleSheet(stylesheet)
+        form_lay.addRow(self.locations_textb)
+
+        # Jump button
+        self.locate_button = QtWidgets.QPushButton(_("Jump to selected position"))
+        self.locate_button.setToolTip(
+            _("Select a position in the Locations text box and then\n"
+              "click this button.")
+        )
+        self.locate_button.setMinimumWidth(60)
+        self.locate_button.setDisabled(True)
+        form_lay.addRow(self.locate_button)
+
+        # Other distances in Gerber
+        self.title_second_res_label = QtWidgets.QLabel('<b>%s:</b>' % _("Other distances"))
+        self.title_second_res_label.setToolTip(_("Will display other distances in the Gerber file ordered from\n"
+                                                 "the minimum to the maximum, not including the absolute minimum."))
+        form_lay.addRow(self.title_second_res_label)
+
+        # Control if to display the locations of where the minimum was found
+        self.sec_locations_cb = FCCheckBox(_("Other distances points coordinates"))
+        self.sec_locations_cb.setToolTip(_("Other distances and the coordinates for points\n"
+                                           "where the distance was found."))
+        form_lay.addRow(self.sec_locations_cb)
+
+        # this way I can hide/show the frame
+        self.sec_locations_frame = QtWidgets.QFrame()
+        self.sec_locations_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.sec_locations_frame)
+        self.distances_box = QtWidgets.QVBoxLayout()
+        self.distances_box.setContentsMargins(0, 0, 0, 0)
+        self.sec_locations_frame.setLayout(self.distances_box)
+
+        # Other Distances label
+        self.distances_label = QtWidgets.QLabel('%s' % _("Gerber distances"))
+        self.distances_label.setToolTip(_("Other distances and the coordinates for points\n"
+                                          "where the distance was found."))
+        self.distances_box.addWidget(self.distances_label)
+
+        # Other distances
+        self.distances_textb = FCTextArea(parent=self)
+        self.distances_textb.setPlaceholderText(
+            _("Other distances and the coordinates for points\n"
+              "where the distance was found.")
+        )
+        self.distances_textb.setReadOnly(True)
+        stylesheet = """
+                                QTextEdit { selection-background-color:blue;
+                                            selection-color:white;
+                                }
+                             """
+
+        self.distances_textb.setStyleSheet(stylesheet)
+        self.distances_box.addWidget(self.distances_textb)
+
+        self.distances_box.addWidget(QtWidgets.QLabel(''))
+
+        # Other Locations label
+        self.locations_label = QtWidgets.QLabel('%s' % _("Points coordinates"))
+        self.locations_label.setToolTip(_("Other distances and the coordinates for points\n"
+                                          "where the distance was found."))
+        self.distances_box.addWidget(self.locations_label)
+
+        # Locations where minimum was found
+        self.locations_sec_textb = FCTextArea(parent=self)
+        self.locations_sec_textb.setPlaceholderText(
+            _("Other distances and the coordinates for points\n"
+              "where the distance was found.")
+        )
+        self.locations_sec_textb.setReadOnly(True)
+        stylesheet = """
+                                QTextEdit { selection-background-color:blue;
+                                            selection-color:white;
+                                }
+                             """
+
+        self.locations_sec_textb.setStyleSheet(stylesheet)
+        self.distances_box.addWidget(self.locations_sec_textb)
+
+        # Jump button
+        self.locate_sec_button = QtWidgets.QPushButton(_("Jump to selected position"))
+        self.locate_sec_button.setToolTip(
+            _("Select a position in the Locations text box and then\n"
+              "click this button.")
+        )
+        self.locate_sec_button.setMinimumWidth(60)
+        self.locate_sec_button.setDisabled(True)
+        self.distances_box.addWidget(self.locate_sec_button)
+
+        # GO button
+        self.calculate_button = QtWidgets.QPushButton(_("Find Minimum"))
+        self.calculate_button.setToolTip(
+            _("Calculate the minimum distance between copper features,\n"
+              "this will allow the determination of the right tool to\n"
+              "use for isolation or copper clearing.")
+        )
+        self.calculate_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        self.calculate_button.setMinimumWidth(60)
+        self.layout.addWidget(self.calculate_button)
+
+        self.layout.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        self.layout.addWidget(self.reset_button)
+
+        self.loc_ois = OptionalHideInputSection(self.locations_cb, [self.locations_textb, self.locate_button])
+        self.sec_loc_ois = OptionalHideInputSection(self.sec_locations_cb, [self.sec_locations_frame])
+
+        # #################################### FINSIHED GUI ###########################
+        # #############################################################################
+
+    def confirmation_message(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
+                                                                                  self.decimals,
+                                                                                  minval,
+                                                                                  self.decimals,
+                                                                                  maxval), False)
+        else:
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+
+    def confirmation_message_int(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                            (_("Edited value is out of range"), minval, maxval), False)
+        else:
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)

+ 183 - 151
appTools/ToolPcbWizard.py

@@ -8,7 +8,7 @@
 from PyQt5 import QtWidgets, QtCore
 from PyQt5 import QtWidgets, QtCore
 
 
 from appTool import AppTool
 from appTool import AppTool
-from appGUI.GUIElements import RadioSet, FCSpinner, FCButton, FCTable
+from appGUI.GUIElements import RadioSet, FCSpinner, FCButton, FCTable, FCLabel
 
 
 import re
 import re
 import os
 import os
@@ -28,121 +28,17 @@ class PcbWizard(AppTool):
 
 
     file_loaded = QtCore.pyqtSignal(str, str)
     file_loaded = QtCore.pyqtSignal(str, str)
 
 
-    toolName = _("PcbWizard Import Tool")
-
     def __init__(self, app):
     def __init__(self, app):
         AppTool.__init__(self, app)
         AppTool.__init__(self, app)
 
 
         self.app = app
         self.app = app
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
-        # Title
-        title_label = QtWidgets.QLabel("%s" % _('Import 2-file Excellon'))
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(title_label)
-
-        self.layout.addWidget(QtWidgets.QLabel(""))
-        self.layout.addWidget(QtWidgets.QLabel("<b>%s:</b>" % _("Load files")))
-
-        # Form Layout
-        form_layout = QtWidgets.QFormLayout()
-        self.layout.addLayout(form_layout)
-
-        self.excellon_label = QtWidgets.QLabel('%s:' % _("Excellon file"))
-        self.excellon_label.setToolTip(
-           _("Load the Excellon file.\n"
-             "Usually it has a .DRL extension")
-        )
-        self.excellon_brn = FCButton(_("Open"))
-        form_layout.addRow(self.excellon_label, self.excellon_brn)
-
-        self.inf_label = QtWidgets.QLabel('%s:' % _("INF file"))
-        self.inf_label.setToolTip(
-            _("Load the INF file.")
-        )
-        self.inf_btn = FCButton(_("Open"))
-        form_layout.addRow(self.inf_label, self.inf_btn)
-
-        self.tools_table = FCTable()
-        self.layout.addWidget(self.tools_table)
-
-        self.tools_table.setColumnCount(2)
-        self.tools_table.setHorizontalHeaderLabels(['#Tool', _('Diameter')])
-
-        self.tools_table.horizontalHeaderItem(0).setToolTip(
-            _("Tool Number"))
-        self.tools_table.horizontalHeaderItem(1).setToolTip(
-            _("Tool diameter in file units."))
-
-        # start with apertures table hidden
-        self.tools_table.setVisible(False)
-
-        self.layout.addWidget(QtWidgets.QLabel(""))
-        self.layout.addWidget(QtWidgets.QLabel("<b>%s:</b>" % _("Excellon format")))
-        # Form Layout
-        form_layout1 = QtWidgets.QFormLayout()
-        self.layout.addLayout(form_layout1)
-
-        # Integral part of the coordinates
-        self.int_entry = FCSpinner(callback=self.confirmation_message_int)
-        self.int_entry.set_range(1, 10)
-        self.int_label = QtWidgets.QLabel('%s:' % _("Int. digits"))
-        self.int_label.setToolTip(
-           _("The number of digits for the integral part of the coordinates.")
-        )
-        form_layout1.addRow(self.int_label, self.int_entry)
-
-        # Fractional part of the coordinates
-        self.frac_entry = FCSpinner(callback=self.confirmation_message_int)
-        self.frac_entry.set_range(1, 10)
-        self.frac_label = QtWidgets.QLabel('%s:' % _("Frac. digits"))
-        self.frac_label.setToolTip(
-            _("The number of digits for the fractional part of the coordinates.")
-        )
-        form_layout1.addRow(self.frac_label, self.frac_entry)
-
-        # Zeros suppression for coordinates
-        self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'LZ'},
-                                     {'label': _('TZ'), 'value': 'TZ'},
-                                     {'label': _('No Suppression'), 'value': 'D'}])
-        self.zeros_label = QtWidgets.QLabel('%s:' % _("Zeros supp."))
-        self.zeros_label.setToolTip(
-            _("The type of zeros suppression used.\n"
-              "Can be of type:\n"
-              "- LZ = leading zeros are kept\n"
-              "- TZ = trailing zeros are kept\n"
-              "- No Suppression = no zero suppression")
-        )
-        form_layout1.addRow(self.zeros_label, self.zeros_radio)
-
-        # Units type
-        self.units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
-                                    {'label': _('MM'), 'value': 'METRIC'}])
-        self.units_label = QtWidgets.QLabel("<b>%s:</b>" % _('Units'))
-        self.units_label.setToolTip(
-            _("The type of units that the coordinates and tool\n"
-              "diameters are using. Can be INCH or MM.")
-        )
-        form_layout1.addRow(self.units_label, self.units_radio)
-
-        # Buttons
-
-        self.import_button = QtWidgets.QPushButton(_("Import Excellon"))
-        self.import_button.setToolTip(
-            _("Import in FlatCAM an Excellon file\n"
-              "that store it's information's in 2 files.\n"
-              "One usually has .DRL extension while\n"
-              "the other has .INF extension.")
-        )
-        self.layout.addWidget(self.import_button)
-
-        self.layout.addStretch()
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = WizardUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
 
 
         self.excellon_loaded = False
         self.excellon_loaded = False
         self.inf_loaded = False
         self.inf_loaded = False
@@ -151,13 +47,13 @@ class PcbWizard(AppTool):
         self.modified_excellon_file = ''
         self.modified_excellon_file = ''
 
 
         # ## Signals
         # ## Signals
-        self.excellon_brn.clicked.connect(self.on_load_excellon_click)
-        self.inf_btn.clicked.connect(self.on_load_inf_click)
-        self.import_button.clicked.connect(lambda: self.on_import_excellon(
+        self.ui.excellon_brn.clicked.connect(self.on_load_excellon_click)
+        self.ui.inf_btn.clicked.connect(self.on_load_inf_click)
+        self.ui.import_button.clicked.connect(lambda: self.on_import_excellon(
             excellon_fileobj=self.modified_excellon_file))
             excellon_fileobj=self.modified_excellon_file))
 
 
         self.file_loaded.connect(self.on_file_loaded)
         self.file_loaded.connect(self.on_file_loaded)
-        self.units_radio.activated_custom.connect(self.on_units_change)
+        self.ui.units_radio.activated_custom.connect(self.ui.on_units_change)
 
 
         self.units = 'INCH'
         self.units = 'INCH'
         self.zeros = 'LZ'
         self.zeros = 'LZ'
@@ -211,10 +107,10 @@ class PcbWizard(AppTool):
         self.tools_from_inf = {}
         self.tools_from_inf = {}
 
 
         # ## Initialize form
         # ## Initialize form
-        self.int_entry.set_value(self.integral)
-        self.frac_entry.set_value(self.fractional)
-        self.zeros_radio.set_value(self.zeros)
-        self.units_radio.set_value(self.units)
+        self.ui.int_entry.set_value(self.integral)
+        self.ui.frac_entry.set_value(self.fractional)
+        self.ui.zeros_radio.set_value(self.zeros)
+        self.ui.units_radio.set_value(self.units)
 
 
         self.excellon_loaded = False
         self.excellon_loaded = False
         self.inf_loaded = False
         self.inf_loaded = False
@@ -227,57 +123,49 @@ class PcbWizard(AppTool):
         sorted_tools = []
         sorted_tools = []
 
 
         if not self.tools_from_inf:
         if not self.tools_from_inf:
-            self.tools_table.setVisible(False)
+            self.ui.tools_table.setVisible(False)
         else:
         else:
             sort = []
             sort = []
             for k, v in list(self.tools_from_inf.items()):
             for k, v in list(self.tools_from_inf.items()):
                 sort.append(int(k))
                 sort.append(int(k))
             sorted_tools = sorted(sort)
             sorted_tools = sorted(sort)
             n = len(sorted_tools)
             n = len(sorted_tools)
-            self.tools_table.setRowCount(n)
+            self.ui.tools_table.setRowCount(n)
 
 
         tool_row = 0
         tool_row = 0
         for tool in sorted_tools:
         for tool in sorted_tools:
             tool_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool))
             tool_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool))
             tool_id_item.setFlags(QtCore.Qt.ItemIsEnabled)
             tool_id_item.setFlags(QtCore.Qt.ItemIsEnabled)
-            self.tools_table.setItem(tool_row, 0, tool_id_item)  # Tool name/id
+            self.ui.tools_table.setItem(tool_row, 0, tool_id_item)  # Tool name/id
 
 
             tool_dia_item = QtWidgets.QTableWidgetItem(str(self.tools_from_inf[tool]))
             tool_dia_item = QtWidgets.QTableWidgetItem(str(self.tools_from_inf[tool]))
             tool_dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
             tool_dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
-            self.tools_table.setItem(tool_row, 1, tool_dia_item)
+            self.ui.tools_table.setItem(tool_row, 1, tool_dia_item)
             tool_row += 1
             tool_row += 1
 
 
-        self.tools_table.resizeColumnsToContents()
-        self.tools_table.resizeRowsToContents()
+        self.ui.tools_table.resizeColumnsToContents()
+        self.ui.tools_table.resizeRowsToContents()
 
 
-        vertical_header = self.tools_table.verticalHeader()
+        vertical_header = self.ui.tools_table.verticalHeader()
         vertical_header.hide()
         vertical_header.hide()
-        self.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+        self.ui.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
 
 
-        horizontal_header = self.tools_table.horizontalHeader()
+        horizontal_header = self.ui.tools_table.horizontalHeader()
         # horizontal_header.setMinimumSectionSize(10)
         # horizontal_header.setMinimumSectionSize(10)
         # horizontal_header.setDefaultSectionSize(70)
         # horizontal_header.setDefaultSectionSize(70)
         horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
         horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
         horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
         horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
 
 
-        self.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-        self.tools_table.setSortingEnabled(False)
-        self.tools_table.setMinimumHeight(self.tools_table.getHeight())
-        self.tools_table.setMaximumHeight(self.tools_table.getHeight())
+        self.ui.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+        self.ui.tools_table.setSortingEnabled(False)
+        self.ui.tools_table.setMinimumHeight(self.ui.tools_table.getHeight())
+        self.ui.tools_table.setMaximumHeight(self.ui.tools_table.getHeight())
 
 
     def update_params(self):
     def update_params(self):
-        self.units = self.units_radio.get_value()
-        self.zeros = self.zeros_radio.get_value()
-        self.integral = self.int_entry.get_value()
-        self.fractional = self.frac_entry.get_value()
-
-    def on_units_change(self, val):
-        if val == 'INCH':
-            self.int_entry.set_value(2)
-            self.frac_entry.set_value(4)
-        else:
-            self.int_entry.set_value(3)
-            self.frac_entry.set_value(3)
+        self.units = self.ui.units_radio.get_value()
+        self.zeros = self.ui.zeros_radio.get_value()
+        self.integral = self.ui.int_entry.get_value()
+        self.fractional = self.ui.frac_entry.get_value()
 
 
     def on_load_excellon_click(self):
     def on_load_excellon_click(self):
         """
         """
@@ -357,9 +245,9 @@ class PcbWizard(AppTool):
                     self.units = 'INCH'
                     self.units = 'INCH'
                 else:
                 else:
                     self.units = 'METRIC'
                     self.units = 'METRIC'
-                self.units_radio.set_value(self.units)
-                self.int_entry.set_value(self.integral)
-                self.frac_entry.set_value(self.fractional)
+                self.ui.units_radio.set_value(self.units)
+                self.ui.int_entry.set_value(self.integral)
+                self.ui.frac_entry.set_value(self.fractional)
 
 
         if not self.tools_from_inf:
         if not self.tools_from_inf:
             self.app.inform.emit('[ERROR] %s' %
             self.app.inform.emit('[ERROR] %s' %
@@ -382,14 +270,12 @@ class PcbWizard(AppTool):
 
 
         if signal == 'inf':
         if signal == 'inf':
             self.inf_loaded = True
             self.inf_loaded = True
-            self.tools_table.setVisible(True)
-            self.app.inform.emit('[success] %s' %
-                                 _("PcbWizard .INF file loaded."))
+            self.ui.tools_table.setVisible(True)
+            self.app.inform.emit('[success] %s' % _("PcbWizard .INF file loaded."))
         elif signal == 'excellon':
         elif signal == 'excellon':
             self.excellon_loaded = True
             self.excellon_loaded = True
             self.outname = os.path.split(str(filename))[1]
             self.outname = os.path.split(str(filename))[1]
-            self.app.inform.emit('[success] %s' %
-                                 _("Main PcbWizard Excellon file loaded."))
+            self.app.inform.emit('[success] %s' % _("Main PcbWizard Excellon file loaded."))
 
 
         if self.excellon_loaded and self.inf_loaded:
         if self.excellon_loaded and self.inf_loaded:
             self.update_params()
             self.update_params()
@@ -467,3 +353,149 @@ class PcbWizard(AppTool):
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _('Excellon merging is in progress. Please wait...'))
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _('Excellon merging is in progress. Please wait...'))
         else:
         else:
             self.app.inform.emit('[ERROR_NOTCL] %s' % _('The imported Excellon file is empty.'))
             self.app.inform.emit('[ERROR_NOTCL] %s' % _('The imported Excellon file is empty.'))
+
+
+class WizardUI:
+    
+    toolName = _("PcbWizard Import Tool")
+
+    def __init__(self, layout, app):
+        self.app = app
+        self.decimals = self.app.decimals
+        self.layout = layout
+
+        # ## Title
+        title_label = FCLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                                QLabel
+                                {
+                                    font-size: 16px;
+                                    font-weight: bold;
+                                }
+                                """)
+        self.layout.addWidget(title_label)
+        self.layout.addWidget(FCLabel(""))
+
+        self.layout.addWidget(FCLabel("<b>%s:</b>" % _("Load files")))
+
+        # Form Layout
+        form_layout = QtWidgets.QFormLayout()
+        self.layout.addLayout(form_layout)
+
+        self.excellon_label = FCLabel('%s:' % _("Excellon file"))
+        self.excellon_label.setToolTip(
+            _("Load the Excellon file.\n"
+              "Usually it has a .DRL extension")
+        )
+        self.excellon_brn = FCButton(_("Open"))
+        form_layout.addRow(self.excellon_label, self.excellon_brn)
+
+        self.inf_label = FCLabel('%s:' % _("INF file"))
+        self.inf_label.setToolTip(
+            _("Load the INF file.")
+        )
+        self.inf_btn = FCButton(_("Open"))
+        form_layout.addRow(self.inf_label, self.inf_btn)
+
+        self.tools_table = FCTable()
+        self.layout.addWidget(self.tools_table)
+
+        self.tools_table.setColumnCount(2)
+        self.tools_table.setHorizontalHeaderLabels(['#Tool', _('Diameter')])
+
+        self.tools_table.horizontalHeaderItem(0).setToolTip(
+            _("Tool Number"))
+        self.tools_table.horizontalHeaderItem(1).setToolTip(
+            _("Tool diameter in file units."))
+
+        # start with apertures table hidden
+        self.tools_table.setVisible(False)
+
+        self.layout.addWidget(FCLabel(""))
+        self.layout.addWidget(FCLabel("<b>%s:</b>" % _("Excellon format")))
+        # Form Layout
+        form_layout1 = QtWidgets.QFormLayout()
+        self.layout.addLayout(form_layout1)
+
+        # Integral part of the coordinates
+        self.int_entry = FCSpinner(callback=self.confirmation_message_int)
+        self.int_entry.set_range(1, 10)
+        self.int_label = FCLabel('%s:' % _("Int. digits"))
+        self.int_label.setToolTip(
+            _("The number of digits for the integral part of the coordinates.")
+        )
+        form_layout1.addRow(self.int_label, self.int_entry)
+
+        # Fractional part of the coordinates
+        self.frac_entry = FCSpinner(callback=self.confirmation_message_int)
+        self.frac_entry.set_range(1, 10)
+        self.frac_label = FCLabel('%s:' % _("Frac. digits"))
+        self.frac_label.setToolTip(
+            _("The number of digits for the fractional part of the coordinates.")
+        )
+        form_layout1.addRow(self.frac_label, self.frac_entry)
+
+        # Zeros suppression for coordinates
+        self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'LZ'},
+                                     {'label': _('TZ'), 'value': 'TZ'},
+                                     {'label': _('No Suppression'), 'value': 'D'}])
+        self.zeros_label = FCLabel('%s:' % _("Zeros supp."))
+        self.zeros_label.setToolTip(
+            _("The type of zeros suppression used.\n"
+              "Can be of type:\n"
+              "- LZ = leading zeros are kept\n"
+              "- TZ = trailing zeros are kept\n"
+              "- No Suppression = no zero suppression")
+        )
+        form_layout1.addRow(self.zeros_label, self.zeros_radio)
+
+        # Units type
+        self.units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
+                                     {'label': _('MM'), 'value': 'METRIC'}])
+        self.units_label = FCLabel("<b>%s:</b>" % _('Units'))
+        self.units_label.setToolTip(
+            _("The type of units that the coordinates and tool\n"
+              "diameters are using. Can be INCH or MM.")
+        )
+        form_layout1.addRow(self.units_label, self.units_radio)
+
+        # Buttons
+
+        self.import_button = QtWidgets.QPushButton(_("Import Excellon"))
+        self.import_button.setToolTip(
+            _("Import in FlatCAM an Excellon file\n"
+              "that store it's information's in 2 files.\n"
+              "One usually has .DRL extension while\n"
+              "the other has .INF extension.")
+        )
+        self.layout.addWidget(self.import_button)
+
+        self.layout.addStretch()
+
+        # #################################### FINSIHED GUI ###########################
+        # #############################################################################
+
+    def on_units_change(self, val):
+        if val == 'INCH':
+            self.int_entry.set_value(2)
+            self.frac_entry.set_value(4)
+        else:
+            self.int_entry.set_value(3)
+            self.frac_entry.set_value(3)
+            
+    def confirmation_message(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
+                                                                                  self.decimals,
+                                                                                  minval,
+                                                                                  self.decimals,
+                                                                                  maxval), False)
+        else:
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+
+    def confirmation_message_int(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                            (_("Edited value is out of range"), minval, maxval), False)
+        else:
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)