瀏覽代碼

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

Marius Stanciu 5 年之前
父節點
當前提交
c45444a772
共有 6 個文件被更改,包括 823 次插入716 次删除
  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
 - Turkish translation strings were updated by the translator, Mehmet Kaya
 - 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
 

+ 25 - 24
appTools/ToolFilm.py

@@ -9,7 +9,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui
 
 from appTool import AppTool
 from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
-    OptionalHideInputSection, FCComboBox, FCFileSaveDialog
+    OptionalHideInputSection, FCComboBox, FCFileSaveDialog, FCButton, FCLabel
 
 from copy import deepcopy
 import logging
@@ -759,7 +759,7 @@ class FilmUI:
         self.layout = layout
 
         # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label = FCLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
                                 QLabel
                                 {
@@ -768,7 +768,7 @@ class FilmUI:
                                 }
                                 """)
         self.layout.addWidget(title_label)
-        self.layout.addWidget(QtWidgets.QLabel(""))
+        self.layout.addWidget(FCLabel(""))
 
         # Form Layout
         grid0 = QtWidgets.QGridLayout()
@@ -781,7 +781,7 @@ class FilmUI:
         self.tf_type_obj_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
                                            {'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(
             _("Specify the type of object for which to create the film.\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'},
                                            {'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(
             _("Specify the type of object to be used as an container for\n"
               "film creation. It can be: Gerber or Geometry type."
@@ -827,7 +827,7 @@ class FilmUI:
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         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(
             _("Sometime the printers will distort the print shape, especially the Laser types.\n"
               "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)
 
-        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.set_range(-999.9999, 999.9999)
         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_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.set_range(-999.9999, 999.9999)
         self.film_scaley_entry.set_precision(self.decimals)
@@ -892,7 +892,7 @@ class FilmUI:
         )
         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.set_range(-999.9999, 999.9999)
         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_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.set_range(-999.9999, 999.9999)
         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_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(
             _("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.")
@@ -957,7 +957,7 @@ class FilmUI:
                                           {'label': _('Y'), 'value': 'y'},
                                           {'label': _('Both'), 'value': 'both'}],
                                          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, 16, 1)
@@ -973,7 +973,7 @@ class FilmUI:
         separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
         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)
 
@@ -983,7 +983,7 @@ class FilmUI:
         self.film_scale_stroke_entry.setSingleStep(0.01)
         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(
             _("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"
@@ -996,7 +996,7 @@ class FilmUI:
         self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
                                    {'label': _('Negative'), 'value': 'neg'}],
                                   stretch=False)
-        self.film_type_label = QtWidgets.QLabel(_("Film Type:"))
+        self.film_type_label = FCLabel(_("Film Type:"))
         self.film_type_label.setToolTip(
             _("Generate a Positive black film or a Negative film.\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.set_precision(self.decimals)
 
-        self.boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
+        self.boundary_label = FCLabel('%s:' % _("Border"))
         self.boundary_label.setToolTip(
             _("Specify a border around the object.\n"
               "Only for negative film.\n"
@@ -1051,7 +1051,7 @@ class FilmUI:
 
         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(
             _("The punch hole source can be:\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_punch, 0, 1)
 
-        self.exc_label = QtWidgets.QLabel('%s:' % _("Excellon Obj"))
+        self.exc_label = FCLabel('%s:' % _("Excellon Obj"))
         self.exc_label.setToolTip(
             _("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_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_spinner = FCDoubleSpinner(callback=self.confirmation_message)
         self.punch_size_spinner.set_range(0, 999.9999)
@@ -1108,7 +1108,7 @@ class FilmUI:
                                          {'label': _('PDF'), 'value': 'pdf'}
                                          ], stretch=False)
 
-        self.file_type_label = QtWidgets.QLabel(_("Film Type:"))
+        self.file_type_label = FCLabel(_("Film Type:"))
         self.file_type_label.setToolTip(
             _("The file type of the saved film. Can be:\n"
               "- 'SVG' -> open-source vectorial format\n"
@@ -1119,7 +1119,7 @@ class FilmUI:
         grid1.addWidget(self.file_type_radio, 1, 1)
 
         # Page orientation
-        self.orientation_label = QtWidgets.QLabel('%s:' % _("Page Orientation"))
+        self.orientation_label = FCLabel('%s:' % _("Page Orientation"))
         self.orientation_label.setToolTip(_("Can be:\n"
                                             "- Portrait\n"
                                             "- Landscape"))
@@ -1132,7 +1132,7 @@ class FilmUI:
         grid1.addWidget(self.orientation_radio, 2, 1)
 
         # 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_combo = FCComboBox()
@@ -1200,7 +1200,8 @@ class FilmUI:
         self.on_film_type(val='hide')
 
         # 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(
             _("Create a Film for the selected object, within\n"
               "the specified box. Does not create a new \n "
@@ -1218,7 +1219,7 @@ class FilmUI:
         self.layout.addStretch()
 
         # ## 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.setToolTip(
             _("Will reset the tool parameters.")

+ 170 - 132
appTools/ToolImage.py

@@ -21,23 +21,165 @@ if '_' not in builtins.__dict__:
 
 class ToolImage(AppTool):
 
-    toolName = _("Image as Object")
-
     def __init__(self, app):
         AppTool.__init__(self, app)
 
         self.app = app
         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("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
+                                QLabel
+                                {
+                                    font-size: 16px;
+                                    font-weight: bold;
+                                }
+                                """)
         self.layout.addWidget(title_label)
 
         # 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.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)
@@ -63,7 +205,7 @@ class ToolImage(AppTool):
         self.dpi_entry = FCSpinner(callback=self.confirmation_message_int)
         self.dpi_entry.set_range(0, 99999)
         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)
 
         self.emty_lbl = QtWidgets.QLabel("")
@@ -150,48 +292,8 @@ class ToolImage(AppTool):
 
         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):
         if val == 'color':
@@ -215,83 +317,19 @@ class ToolImage(AppTool):
             self.mask_bw_label.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:
-            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:
-            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 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
 
@@ -28,133 +28,20 @@ log = logging.getLogger('base')
 
 class ToolInvertGerber(AppTool):
 
-    toolName = _("Invert Gerber Tool")
-
     def __init__(self, app):
         self.app = app
         self.decimals = self.app.decimals
 
         AppTool.__init__(self, app)
 
-        self.tools_frame = QtWidgets.QFrame()
-        self.tools_frame.setContentsMargins(0, 0, 0, 0)
-        self.layout.addWidget(self.tools_frame)
-        self.tools_box = QtWidgets.QVBoxLayout()
-        self.tools_box.setContentsMargins(0, 0, 0, 0)
-        self.tools_frame.setLayout(self.tools_box)
-
-        # Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.tools_box.addWidget(title_label)
-
-        # Grid Layout
-        grid0 = QtWidgets.QGridLayout()
-        grid0.setColumnStretch(0, 0)
-        grid0.setColumnStretch(1, 1)
-        self.tools_box.addLayout(grid0)
-
-        grid0.addWidget(QtWidgets.QLabel(''), 0, 0, 1, 2)
-
-        # Target Gerber Object
-        self.gerber_combo = FCComboBox()
-        self.gerber_combo.setModel(self.app.collection)
-        self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.gerber_combo.is_last = True
-        self.gerber_combo.obj_type = "Gerber"
-
-        self.gerber_label = QtWidgets.QLabel('<b>%s:</b>' % _("GERBER"))
-        self.gerber_label.setToolTip(
-            _("Gerber object that will be inverted.")
-        )
+        # #############################################################################
+        # ######################### 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):
         AppTool.install(self, icon, separator, shortcut='', **kwargs)
@@ -188,20 +75,20 @@ class ToolInvertGerber(AppTool):
         self.app.ui.notebook.setTabText(2, _("Invert Tool"))
 
     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):
-        margin = self.margin_entry.get_value()
+        margin = self.ui.margin_entry.get_value()
         if round(margin, self.decimals) == 0.0:
             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:
             join_style = 'r'
 
         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"
 
@@ -243,32 +130,6 @@ class ToolInvertGerber(AppTool):
 
         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:
             new_apertures['0'] = {}
             new_apertures['0']['type'] = 'C'
@@ -302,9 +163,155 @@ class ToolInvertGerber(AppTool):
         self.app.app_obj.new_object('gerber', outname, init_func)
 
     def reset_fields(self):
-        self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.ui.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
 
     @staticmethod
     def poly2rings(poly):
         return [poly.exterior] + [interior for interior in poly.interiors]
-# end of file
+
+
+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):
 
-    toolName = _("Optimal Tool")
-
     update_text = QtCore.pyqtSignal(list)
     update_sec_distances = QtCore.pyqtSignal(dict)
 
@@ -41,222 +39,11 @@ class ToolOptimal(AppTool):
         self.units = self.app.defaults['units'].upper()
         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
         self.selected_text = ''
@@ -271,17 +58,17 @@ class ToolOptimal(AppTool):
         # ############################################################################
         # ############################ 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):
         AppTool.install(self, icon, separator, shortcut='Alt+O', **kwargs)
@@ -314,13 +101,13 @@ class ToolOptimal(AppTool):
         self.app.ui.notebook.setTabText(2, _("Optimal Tool"))
 
     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
-        cursor = self.locations_textb.textCursor()
+        cursor = self.ui.locations_textb.textCursor()
         cursor.select(QtGui.QTextCursor.Document)
 
         # clear previous selection highlight
@@ -328,20 +115,20 @@ class ToolOptimal(AppTool):
         tmp.clearBackground()
         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()
 
     def find_minimum_distance(self):
         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:
             fcobj = model_index.internalPointer().obj
         except Exception as e:
@@ -426,17 +213,16 @@ class ToolOptimal(AppTool):
                             old_disp_number = disp_number
                     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_dist = min(min_list)
                 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 = '%d' % int(freq)
-                self.freq_entry.set_value(freq)
+                self.ui.freq_entry.set_value(freq)
 
                 min_locations = self.min_dict.pop(min_dist)
 
@@ -484,12 +270,12 @@ class ToolOptimal(AppTool):
         for loc in data:
             if loc:
                 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):
         # new cursor - select all document
-        cursor = self.locations_textb.textCursor()
+        cursor = self.ui.locations_textb.textCursor()
         cursor.select(QtGui.QTextCursor.Document)
 
         # clear previous selection highlight
@@ -498,7 +284,7 @@ class ToolOptimal(AppTool):
         cursor.setBlockFormat(tmp)
 
         # new cursor - select the current line
-        cursor = self.locations_textb.textCursor()
+        cursor = self.ui.locations_textb.textCursor()
         cursor.select(QtGui.QTextCursor.LineUnderCursor)
 
         # highlight the current selected line
@@ -513,12 +299,12 @@ class ToolOptimal(AppTool):
         txt = ''
         for loc in distance_list:
             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):
         # new cursor - select all document
-        cursor = self.distances_textb.textCursor()
+        cursor = self.ui.distances_textb.textCursor()
         cursor.select(QtGui.QTextCursor.Document)
 
         # clear previous selection highlight
@@ -527,7 +313,7 @@ class ToolOptimal(AppTool):
         cursor.setBlockFormat(tmp)
 
         # new cursor - select the current line
-        cursor = self.distances_textb.textCursor()
+        cursor = self.ui.distances_textb.textCursor()
         cursor.select(QtGui.QTextCursor.LineUnderCursor)
 
         # highlight the current selected line
@@ -545,11 +331,11 @@ class ToolOptimal(AppTool):
         for loc in distance_list:
             if loc:
                 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):
         # new cursor - select all document
-        cursor = self.locations_sec_textb.textCursor()
+        cursor = self.ui.locations_sec_textb.textCursor()
         cursor.select(QtGui.QTextCursor.Document)
 
         # clear previous selection highlight
@@ -558,7 +344,7 @@ class ToolOptimal(AppTool):
         cursor.setBlockFormat(tmp)
 
         # new cursor - select the current line
-        cursor = self.locations_sec_textb.textCursor()
+        cursor = self.ui.locations_sec_textb.textCursor()
         cursor.select(QtGui.QTextCursor.LineUnderCursor)
 
         # highlight the current selected line
@@ -593,5 +379,247 @@ class ToolOptimal(AppTool):
             return
 
     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.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 appTool import AppTool
-from appGUI.GUIElements import RadioSet, FCSpinner, FCButton, FCTable
+from appGUI.GUIElements import RadioSet, FCSpinner, FCButton, FCTable, FCLabel
 
 import re
 import os
@@ -28,121 +28,17 @@ class PcbWizard(AppTool):
 
     file_loaded = QtCore.pyqtSignal(str, str)
 
-    toolName = _("PcbWizard Import Tool")
-
     def __init__(self, app):
         AppTool.__init__(self, app)
 
         self.app = app
         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.inf_loaded = False
@@ -151,13 +47,13 @@ class PcbWizard(AppTool):
         self.modified_excellon_file = ''
 
         # ## 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))
 
         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.zeros = 'LZ'
@@ -211,10 +107,10 @@ class PcbWizard(AppTool):
         self.tools_from_inf = {}
 
         # ## 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.inf_loaded = False
@@ -227,57 +123,49 @@ class PcbWizard(AppTool):
         sorted_tools = []
 
         if not self.tools_from_inf:
-            self.tools_table.setVisible(False)
+            self.ui.tools_table.setVisible(False)
         else:
             sort = []
             for k, v in list(self.tools_from_inf.items()):
                 sort.append(int(k))
             sorted_tools = sorted(sort)
             n = len(sorted_tools)
-            self.tools_table.setRowCount(n)
+            self.ui.tools_table.setRowCount(n)
 
         tool_row = 0
         for tool in sorted_tools:
             tool_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool))
             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.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
 
-        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()
-        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.setDefaultSectionSize(70)
         horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
         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):
-        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):
         """
@@ -357,9 +245,9 @@ class PcbWizard(AppTool):
                     self.units = 'INCH'
                 else:
                     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:
             self.app.inform.emit('[ERROR] %s' %
@@ -382,14 +270,12 @@ class PcbWizard(AppTool):
 
         if signal == 'inf':
             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':
             self.excellon_loaded = True
             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:
             self.update_params()
@@ -467,3 +353,149 @@ class PcbWizard(AppTool):
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _('Excellon merging is in progress. Please wait...'))
         else:
             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)