Pārlūkot izejas kodu

- for Tools: Calculators, Calibration, Copper Thieving, Corners, Fiducials - moved the Tool UI in its own class

Marius Stanciu 5 gadi atpakaļ
vecāks
revīzija
c3b99c3e33
7 mainītis faili ar 3249 papildinājumiem un 3089 dzēšanām
  1. 1 0
      CHANGELOG.md
  2. 156 124
      appTools/ToolCalculators.py
  3. 1126 1096
      appTools/ToolCalibration.py
  4. 1311 1283
      appTools/ToolCopperThieving.py
  5. 207 173
      appTools/ToolCorners.py
  6. 0 1
      appTools/ToolDblSided.py
  7. 448 412
      appTools/ToolFiducials.py

+ 1 - 0
CHANGELOG.md

@@ -14,6 +14,7 @@ CHANGELOG for FlatCAM beta
 - in CNCJob UI Autolevelling - adding manual probe points now show some geometry (circles) for the added points until the adding is finished
 - 2Sided Tool - finished the feature that allows picking an Excellon drill hole center as a Point mirror reference
 - Tool Align Objects - moved the Tool Ui into its own class
+- for Tools: Calculators, Calibration, Copper Thieving, Corners, Fiducials - moved the Tool UI in its own class
 
 24.08.2020
 

+ 156 - 124
appTools/ToolCalculators.py

@@ -21,26 +21,152 @@ if '_' not in builtins.__dict__:
 
 class ToolCalculator(AppTool):
 
+    def __init__(self, app):
+        AppTool.__init__(self, app)
+
+        self.app = app
+        self.decimals = self.app.decimals
+
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = CalcUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
+
+        self.units = ''
+
+        # ## Signals
+        self.ui.cutDepth_entry.valueChanged.connect(self.on_calculate_tool_dia)
+        self.ui.cutDepth_entry.returnPressed.connect(self.on_calculate_tool_dia)
+        self.ui.tipDia_entry.returnPressed.connect(self.on_calculate_tool_dia)
+        self.ui.tipAngle_entry.returnPressed.connect(self.on_calculate_tool_dia)
+        self.ui.calculate_vshape_button.clicked.connect(self.on_calculate_tool_dia)
+
+        self.ui.mm_entry.editingFinished.connect(self.on_calculate_inch_units)
+        self.ui.inch_entry.editingFinished.connect(self.on_calculate_mm_units)
+
+        self.ui.calculate_plate_button.clicked.connect(self.on_calculate_eplate)
+        self.ui.reset_button.clicked.connect(self.set_tool_ui)
+
+    def run(self, toggle=True):
+        self.app.defaults.report_usage("ToolCalculators()")
+
+        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, _("Calc. Tool"))
+
+    def install(self, icon=None, separator=None, **kwargs):
+        AppTool.install(self, icon, separator, shortcut='Alt+C', **kwargs)
+
+    def set_tool_ui(self):
+        self.units = self.app.defaults['units'].upper()
+
+        # ## Initialize form
+        self.ui.mm_entry.set_value('%.*f' % (self.decimals, 0))
+        self.ui.inch_entry.set_value('%.*f' % (self.decimals, 0))
+
+        length = self.app.defaults["tools_calc_electro_length"]
+        width = self.app.defaults["tools_calc_electro_width"]
+        density = self.app.defaults["tools_calc_electro_cdensity"]
+        growth = self.app.defaults["tools_calc_electro_growth"]
+        self.ui.pcblength_entry.set_value(length)
+        self.ui.pcbwidth_entry.set_value(width)
+        self.ui.cdensity_entry.set_value(density)
+        self.ui.growth_entry.set_value(growth)
+        self.ui.cvalue_entry.set_value(0.00)
+        self.ui.time_entry.set_value(0.0)
+
+        tip_dia = self.app.defaults["tools_calc_vshape_tip_dia"]
+        tip_angle = self.app.defaults["tools_calc_vshape_tip_angle"]
+        cut_z = self.app.defaults["tools_calc_vshape_cut_z"]
+
+        self.ui.tipDia_entry.set_value(tip_dia)
+        self.ui.tipAngle_entry.set_value(tip_angle)
+        self.ui.cutDepth_entry.set_value(cut_z)
+        self.ui.effectiveToolDia_entry.set_value('0.0000')
+
+    def on_calculate_tool_dia(self):
+        # Calculation:
+        # Manufacturer gives total angle of the the tip but we need only half of it
+        # tangent(half_tip_angle) = opposite side / adjacent = part_of _real_dia / depth_of_cut
+        # effective_diameter = tip_diameter + part_of_real_dia_left_side + part_of_real_dia_right_side
+        # tool is symmetrical therefore: part_of_real_dia_left_side = part_of_real_dia_right_side
+        # effective_diameter = tip_diameter + (2 * part_of_real_dia_left_side)
+        # effective diameter = tip_diameter + (2 * depth_of_cut * tangent(half_tip_angle))
+
+        tip_diameter = float(self.ui.tipDia_entry.get_value())
+
+        half_tip_angle = float(self.ui.tipAngle_entry.get_value()) / 2.0
+
+        cut_depth = float(self.ui.cutDepth_entry.get_value())
+        cut_depth = -cut_depth if cut_depth < 0 else cut_depth
+
+        tool_diameter = tip_diameter + (2 * cut_depth * math.tan(math.radians(half_tip_angle)))
+        self.ui.effectiveToolDia_entry.set_value("%.*f" % (self.decimals, tool_diameter))
+
+    def on_calculate_inch_units(self):
+        mm_val = float(self.mm_entry.get_value())
+        self.ui.inch_entry.set_value('%.*f' % (self.decimals, (mm_val / 25.4)))
+
+    def on_calculate_mm_units(self):
+        inch_val = float(self.inch_entry.get_value())
+        self.ui.mm_entry.set_value('%.*f' % (self.decimals, (inch_val * 25.4)))
+
+    def on_calculate_eplate(self):
+        length = float(self.ui.pcblength_entry.get_value())
+        width = float(self.ui.pcbwidth_entry.get_value())
+        density = float(self.ui.cdensity_entry.get_value())
+        copper = float(self.ui.growth_entry.get_value())
+
+        calculated_current = (length * width * density) * 0.0021527820833419
+        calculated_time = copper * 2.142857142857143 * float(20 / density)
+
+        self.ui.cvalue_entry.set_value('%.2f' % calculated_current)
+        self.ui.time_entry.set_value('%.1f' % calculated_time)
+
+
+class CalcUI:
+
     toolName = _("Calculators")
     v_shapeName = _("V-Shape Tool Calculator")
     unitsName = _("Units Calculator")
     eplateName = _("ElectroPlating Calculator")
 
-    def __init__(self, app):
-        AppTool.__init__(self, app)
-
+    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)
 
         # #####################
@@ -249,123 +375,29 @@ class ToolCalculator(AppTool):
             _("Will reset the tool parameters.")
         )
         self.reset_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
         self.layout.addWidget(self.reset_button)
 
-        self.units = ''
-
-        # ## Signals
-        self.cutDepth_entry.valueChanged.connect(self.on_calculate_tool_dia)
-        self.cutDepth_entry.returnPressed.connect(self.on_calculate_tool_dia)
-        self.tipDia_entry.returnPressed.connect(self.on_calculate_tool_dia)
-        self.tipAngle_entry.returnPressed.connect(self.on_calculate_tool_dia)
-        self.calculate_vshape_button.clicked.connect(self.on_calculate_tool_dia)
-
-        self.mm_entry.editingFinished.connect(self.on_calculate_inch_units)
-        self.inch_entry.editingFinished.connect(self.on_calculate_mm_units)
+        # #################################### FINSIHED GUI ###########################
+        # #############################################################################
 
-        self.calculate_plate_button.clicked.connect(self.on_calculate_eplate)
-        self.reset_button.clicked.connect(self.set_tool_ui)
-
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolCalculators()")
-
-        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
+    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:
-            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, _("Calc. Tool"))
-
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Alt+C', **kwargs)
-
-    def set_tool_ui(self):
-        self.units = self.app.defaults['units'].upper()
-
-        # ## Initialize form
-        self.mm_entry.set_value('%.*f' % (self.decimals, 0))
-        self.inch_entry.set_value('%.*f' % (self.decimals, 0))
-
-        length = self.app.defaults["tools_calc_electro_length"]
-        width = self.app.defaults["tools_calc_electro_width"]
-        density = self.app.defaults["tools_calc_electro_cdensity"]
-        growth = self.app.defaults["tools_calc_electro_growth"]
-        self.pcblength_entry.set_value(length)
-        self.pcbwidth_entry.set_value(width)
-        self.cdensity_entry.set_value(density)
-        self.growth_entry.set_value(growth)
-        self.cvalue_entry.set_value(0.00)
-        self.time_entry.set_value(0.0)
-
-        tip_dia = self.app.defaults["tools_calc_vshape_tip_dia"]
-        tip_angle = self.app.defaults["tools_calc_vshape_tip_angle"]
-        cut_z = self.app.defaults["tools_calc_vshape_cut_z"]
-
-        self.tipDia_entry.set_value(tip_dia)
-        self.tipAngle_entry.set_value(tip_angle)
-        self.cutDepth_entry.set_value(cut_z)
-        self.effectiveToolDia_entry.set_value('0.0000')
-
-    def on_calculate_tool_dia(self):
-        # Calculation:
-        # Manufacturer gives total angle of the the tip but we need only half of it
-        # tangent(half_tip_angle) = opposite side / adjacent = part_of _real_dia / depth_of_cut
-        # effective_diameter = tip_diameter + part_of_real_dia_left_side + part_of_real_dia_right_side
-        # tool is symmetrical therefore: part_of_real_dia_left_side = part_of_real_dia_right_side
-        # effective_diameter = tip_diameter + (2 * part_of_real_dia_left_side)
-        # effective diameter = tip_diameter + (2 * depth_of_cut * tangent(half_tip_angle))
-
-        tip_diameter = float(self.tipDia_entry.get_value())
-
-        half_tip_angle = float(self.tipAngle_entry.get_value()) / 2.0
-
-        cut_depth = float(self.cutDepth_entry.get_value())
-        cut_depth = -cut_depth if cut_depth < 0 else cut_depth
-
-        tool_diameter = tip_diameter + (2 * cut_depth * math.tan(math.radians(half_tip_angle)))
-        self.effectiveToolDia_entry.set_value("%.*f" % (self.decimals, tool_diameter))
-
-    def on_calculate_inch_units(self):
-        mm_val = float(self.mm_entry.get_value())
-        self.inch_entry.set_value('%.*f' % (self.decimals, (mm_val / 25.4)))
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
-    def on_calculate_mm_units(self):
-        inch_val = float(self.inch_entry.get_value())
-        self.mm_entry.set_value('%.*f' % (self.decimals, (inch_val * 25.4)))
-
-    def on_calculate_eplate(self):
-        length = float(self.pcblength_entry.get_value())
-        width = float(self.pcbwidth_entry.get_value())
-        density = float(self.cdensity_entry.get_value())
-        copper = float(self.growth_entry.get_value())
-
-        calculated_current = (length * width * density) * 0.0021527820833419
-        calculated_time = copper * 2.142857142857143 * float(20 / density)
-
-        self.cvalue_entry.set_value('%.2f' % calculated_current)
-        self.time_entry.set_value('%.1f' % calculated_time)
-
-# end of file
+    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)

+ 1126 - 1096
appTools/ToolCalibration.py

@@ -34,8 +34,6 @@ log = logging.getLogger('base')
 
 class ToolCalibration(AppTool):
 
-    toolName = _("Calibration Tool")
-
     def __init__(self, app):
         AppTool.__init__(self, app)
 
@@ -44,1335 +42,1367 @@ class ToolCalibration(AppTool):
 
         self.decimals = self.app.decimals
 
-        # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(title_label)
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = CalibUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
 
-        self.layout.addWidget(QtWidgets.QLabel(''))
+        self.mr = None
+        self.units = ''
 
-        # ## Grid Layout
-        grid_lay = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_lay)
-        grid_lay.setColumnStretch(0, 0)
-        grid_lay.setColumnStretch(1, 1)
-        grid_lay.setColumnStretch(2, 0)
+        # here store 4 points to be used for calibration
+        self.click_points = [[], [], [], []]
 
-        self.gcode_title_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
-        self.gcode_title_label.setToolTip(
-            _("Parameters used when creating the GCode in this tool.")
-        )
-        grid_lay.addWidget(self.gcode_title_label, 0, 0, 1, 3)
+        # store the status of the grid
+        self.grid_status_memory = None
 
-        # Travel Z entry
-        travelz_lbl = QtWidgets.QLabel('%s:' % _("Travel Z"))
-        travelz_lbl.setToolTip(
-            _("Height (Z) for travelling between the points.")
-        )
+        self.target_obj = None
 
-        self.travelz_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.travelz_entry.set_range(-9999.9999, 9999.9999)
-        self.travelz_entry.set_precision(self.decimals)
-        self.travelz_entry.setSingleStep(0.1)
+        # if the mouse events are connected to a local method set this True
+        self.local_connected = False
 
-        grid_lay.addWidget(travelz_lbl, 1, 0)
-        grid_lay.addWidget(self.travelz_entry, 1, 1, 1, 2)
+        # reference for the tab where to open and view the verification GCode
+        self.gcode_editor_tab = None
 
-        # Verification Z entry
-        verz_lbl = QtWidgets.QLabel('%s:' % _("Verification Z"))
-        verz_lbl.setToolTip(
-            _("Height (Z) for checking the point.")
-        )
+        # calibrated object
+        self.cal_object = None
 
-        self.verz_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.verz_entry.set_range(-9999.9999, 9999.9999)
-        self.verz_entry.set_precision(self.decimals)
-        self.verz_entry.setSingleStep(0.1)
+        # ## Signals
+        self.ui.cal_source_radio.activated_custom.connect(self.on_cal_source_radio)
+        self.ui.obj_type_combo.currentIndexChanged.connect(self.on_obj_type_combo)
+        self.ui.adj_object_type_combo.currentIndexChanged.connect(self.on_adj_obj_type_combo)
 
-        grid_lay.addWidget(verz_lbl, 2, 0)
-        grid_lay.addWidget(self.verz_entry, 2, 1, 1, 2)
+        self.ui.start_button.clicked.connect(self.on_start_collect_points)
 
-        # Zero the Z of the verification tool
-        self.zeroz_cb = FCCheckBox('%s' % _("Zero Z tool"))
-        self.zeroz_cb.setToolTip(
-            _("Include a sequence to zero the height (Z)\n"
-              "of the verification tool.")
-        )
+        self.ui.gcode_button.clicked.connect(self.generate_verification_gcode)
+        self.ui.adj_gcode_button.clicked.connect(self.generate_verification_gcode)
 
-        grid_lay.addWidget(self.zeroz_cb, 3, 0, 1, 3)
+        self.ui.generate_factors_button.clicked.connect(self.calculate_factors)
 
-        # Toolchange Z entry
-        toolchangez_lbl = QtWidgets.QLabel('%s:' % _("Toolchange Z"))
-        toolchangez_lbl.setToolTip(
-            _("Height (Z) for mounting the verification probe.")
-        )
+        self.ui.scale_button.clicked.connect(self.on_scale_button)
+        self.ui.skew_button.clicked.connect(self.on_skew_button)
 
-        self.toolchangez_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.toolchangez_entry.set_range(0.0000, 9999.9999)
-        self.toolchangez_entry.set_precision(self.decimals)
-        self.toolchangez_entry.setSingleStep(0.1)
+        self.ui.cal_button.clicked.connect(self.on_cal_button_click)
+        self.ui.reset_button.clicked.connect(self.set_tool_ui)
 
-        grid_lay.addWidget(toolchangez_lbl, 4, 0)
-        grid_lay.addWidget(self.toolchangez_entry, 4, 1, 1, 2)
+    def run(self, toggle=True):
+        self.app.defaults.report_usage("ToolCalibration()")
 
-        # Toolchange X-Y entry
-        toolchangexy_lbl = QtWidgets.QLabel('%s:' % _('Toolchange X-Y'))
-        toolchangexy_lbl.setToolTip(
-            _("Toolchange X,Y position.\n"
-              "If no value is entered then the current\n"
-              "(x, y) point will be used,")
-        )
+        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])
 
-        self.toolchange_xy_entry = FCEntry()
+        AppTool.run(self)
 
-        grid_lay.addWidget(toolchangexy_lbl, 5, 0)
-        grid_lay.addWidget(self.toolchange_xy_entry, 5, 1, 1, 2)
+        self.set_tool_ui()
 
-        self.z_ois = OptionalInputSection(
-            self.zeroz_cb,
-            [
-                toolchangez_lbl,
-                self.toolchangez_entry,
-                toolchangexy_lbl,
-                self.toolchange_xy_entry
-            ]
-        )
+        self.app.ui.notebook.setTabText(2, _("Calibration Tool"))
 
-        separator_line1 = QtWidgets.QFrame()
-        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line1, 6, 0, 1, 3)
+    def install(self, icon=None, separator=None, **kwargs):
+        AppTool.install(self, icon, separator, shortcut='Alt+E', **kwargs)
 
-        # Second point choice
-        second_point_lbl = QtWidgets.QLabel('%s:' % _("Second point"))
-        second_point_lbl.setToolTip(
-            _("Second point in the Gcode verification can be:\n"
-              "- top-left -> the user will align the PCB vertically\n"
-              "- bottom-right -> the user will align the PCB horizontally")
-        )
-        self.second_point_radio = RadioSet([{'label': _('Top-Left'), 'value': 'tl'},
-                                            {'label': _('Bottom-Right'), 'value': 'br'}],
-                                           orientation='vertical')
+    def set_tool_ui(self):
+        self.units = self.app.defaults['units'].upper()
 
-        grid_lay.addWidget(second_point_lbl, 7, 0)
-        grid_lay.addWidget(self.second_point_radio, 7, 1, 1, 2)
+        if self.local_connected is True:
+            self.disconnect_cal_events()
 
-        separator_line1 = QtWidgets.QFrame()
-        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line1, 8, 0, 1, 3)
+        self.ui.bottom_left_coordx_found.set_value(_("Origin"))
+        self.ui.bottom_left_coordy_found.set_value(_("Origin"))
 
-        grid_lay.addWidget(QtWidgets.QLabel(''), 9, 0, 1, 3)
+        self.reset_calibration_points()
 
-        step_1 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 1: Acquire Calibration Points"))
-        step_1.setToolTip(
-            _("Pick four points by clicking on canvas.\n"
-              "Those four points should be in the four\n"
-              "(as much as possible) corners of the object.")
-        )
-        grid_lay.addWidget(step_1, 10, 0, 1, 3)
+        self.ui.cal_source_radio.set_value(self.app.defaults['tools_cal_calsource'])
+        self.ui.travelz_entry.set_value(self.app.defaults['tools_cal_travelz'])
+        self.ui.verz_entry.set_value(self.app.defaults['tools_cal_verz'])
+        self.ui.zeroz_cb.set_value(self.app.defaults['tools_cal_zeroz'])
+        self.ui.toolchangez_entry.set_value(self.app.defaults['tools_cal_toolchangez'])
+        self.ui.toolchange_xy_entry.set_value(self.app.defaults['tools_cal_toolchange_xy'])
 
-        self.cal_source_lbl = QtWidgets.QLabel("<b>%s:</b>" % _("Source Type"))
-        self.cal_source_lbl.setToolTip(_("The source of calibration points.\n"
-                                         "It can be:\n"
-                                         "- Object -> click a hole geo for Excellon or a pad for Gerber\n"
-                                         "- Free -> click freely on canvas to acquire the calibration points"))
-        self.cal_source_radio = RadioSet([{'label': _('Object'), 'value': 'object'},
-                                          {'label': _('Free'), 'value': 'free'}],
-                                         stretch=False)
+        self.ui.second_point_radio.set_value(self.app.defaults['tools_cal_sec_point'])
 
-        grid_lay.addWidget(self.cal_source_lbl, 11, 0)
-        grid_lay.addWidget(self.cal_source_radio, 11, 1, 1, 2)
+        self.ui.scalex_entry.set_value(1.0)
+        self.ui.scaley_entry.set_value(1.0)
+        self.ui.skewx_entry.set_value(0.0)
+        self.ui.skewy_entry.set_value(0.0)
 
-        self.obj_type_label = QtWidgets.QLabel("%s:" % _("Object Type"))
+        # default object selection is Excellon = index_1
+        self.ui.obj_type_combo.setCurrentIndex(1)
+        self.on_obj_type_combo()
 
-        self.obj_type_combo = FCComboBox()
-        self.obj_type_combo.addItem(_("Gerber"))
-        self.obj_type_combo.addItem(_("Excellon"))
+        self.ui.adj_object_type_combo.setCurrentIndex(0)
+        self.on_adj_obj_type_combo()
+        # self.adj_object_combo.setCurrentIndex(0)
 
-        self.obj_type_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
-        self.obj_type_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
+        # calibrated object
+        self.cal_object = None
 
-        grid_lay.addWidget(self.obj_type_label, 12, 0)
-        grid_lay.addWidget(self.obj_type_combo, 12, 1, 1, 2)
+        self.app.inform.emit('%s...' % _("Tool initialized"))
 
-        self.object_combo = FCComboBox()
-        self.object_combo.setModel(self.app.collection)
-        self.object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
-        self.object_combo.is_last = True
+    def on_obj_type_combo(self):
+        obj_type = self.ui.obj_type_combo.currentIndex()
+        self.ui.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        # self.object_combo.setCurrentIndex(0)
+        self.ui.object_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon"
+        }[self.ui.obj_type_combo.get_value()]
 
-        self.object_label = QtWidgets.QLabel("%s:" % _("Source object selection"))
-        self.object_label.setToolTip(
-            _("FlatCAM Object to be used as a source for reference points.")
-        )
+    def on_adj_obj_type_combo(self):
+        obj_type = self.ui.adj_object_type_combo.currentIndex()
+        self.ui.adj_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        # self.adj_object_combo.setCurrentIndex(0)
+        self.ui.adj_object_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.ui.adj_object_type_combo.get_value()]
 
-        grid_lay.addWidget(self.object_label, 13, 0, 1, 3)
-        grid_lay.addWidget(self.object_combo, 14, 0, 1, 3)
+    def on_cal_source_radio(self, val):
+        if val == 'object':
+            self.ui.obj_type_label.setDisabled(False)
+            self.ui.obj_type_combo.setDisabled(False)
+            self.ui.object_label.setDisabled(False)
+            self.ui.object_combo.setDisabled(False)
+        else:
+            self.ui.obj_type_label.setDisabled(True)
+            self.ui.obj_type_combo.setDisabled(True)
+            self.ui.object_label.setDisabled(True)
+            self.ui.object_combo.setDisabled(True)
 
-        self.points_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Calibration Points'))
-        self.points_table_label.setToolTip(
-            _("Contain the expected calibration points and the\n"
-              "ones measured.")
-        )
-        grid_lay.addWidget(self.points_table_label, 15, 0, 1, 3)
+    def on_start_collect_points(self):
 
-        self.points_table = FCTable()
-        self.points_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
-        # self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
-        grid_lay.addWidget(self.points_table, 16, 0, 1, 3)
+        if self.ui.cal_source_radio.get_value() == 'object':
+            selection_index = self.ui.object_combo.currentIndex()
+            model_index = self.app.collection.index(selection_index, 0, self.ui.object_combo.rootModelIndex())
+            try:
+                self.target_obj = model_index.internalPointer().obj
+            except AttributeError:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no source FlatCAM object selected..."))
+                return
 
-        self.points_table.setColumnCount(4)
-        self.points_table.setHorizontalHeaderLabels(
-            [
-                '#',
-                _("Name"),
-                _("Target"),
-                _("Found Delta")
-            ]
-        )
-        self.points_table.setRowCount(8)
-        row = 0
+        # disengage the grid snapping since it will be hard to find the drills on grid
+        if self.app.ui.grid_snap_btn.isChecked():
+            self.grid_status_memory = True
+            self.app.ui.grid_snap_btn.trigger()
+        else:
+            self.grid_status_memory = False
 
-        # BOTTOM LEFT
-        id_item_1 = QtWidgets.QTableWidgetItem('%d' % 1)
-        flags = QtCore.Qt.ItemIsEnabled
-        id_item_1.setFlags(flags)
-        self.points_table.setItem(row, 0, id_item_1)  # Tool name/id
+        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
 
-        self.bottom_left_coordx_lbl = QtWidgets.QLabel('%s' % _('Bot Left X'))
-        self.points_table.setCellWidget(row, 1, self.bottom_left_coordx_lbl)
-        self.bottom_left_coordx_tgt = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.bottom_left_coordx_tgt)
-        self.bottom_left_coordx_tgt.setReadOnly(True)
-        self.bottom_left_coordx_found = EvalEntry()
-        self.points_table.setCellWidget(row, 3, self.bottom_left_coordx_found)
-        row += 1
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+        else:
+            self.canvas.graph_event_disconnect(self.app.mr)
 
-        self.bottom_left_coordy_lbl = QtWidgets.QLabel('%s' % _('Bot Left Y'))
-        self.points_table.setCellWidget(row, 1, self.bottom_left_coordy_lbl)
-        self.bottom_left_coordy_tgt = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.bottom_left_coordy_tgt)
-        self.bottom_left_coordy_tgt.setReadOnly(True)
-        self.bottom_left_coordy_found = EvalEntry()
-        self.points_table.setCellWidget(row, 3, self.bottom_left_coordy_found)
+        self.local_connected = True
 
-        self.bottom_left_coordx_found.setDisabled(True)
-        self.bottom_left_coordy_found.setDisabled(True)
-        row += 1
+        self.reset_calibration_points()
 
-        # BOTTOM RIGHT
-        id_item_2 = QtWidgets.QTableWidgetItem('%d' % 2)
-        flags = QtCore.Qt.ItemIsEnabled
-        id_item_2.setFlags(flags)
-        self.points_table.setItem(row, 0, id_item_2)  # Tool name/id
+        self.app.inform.emit(_("Get First calibration point. Bottom Left..."))
 
-        self.bottom_right_coordx_lbl = QtWidgets.QLabel('%s' % _('Bot Right X'))
-        self.points_table.setCellWidget(row, 1, self.bottom_right_coordx_lbl)
-        self.bottom_right_coordx_tgt = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.bottom_right_coordx_tgt)
-        self.bottom_right_coordx_tgt.setReadOnly(True)
-        self.bottom_right_coordx_found = EvalEntry()
-        self.points_table.setCellWidget(row, 3, self.bottom_right_coordx_found)
+    def on_mouse_click_release(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            right_button = 2
+            self.app.event_is_dragging = self.app.event_is_dragging
+        else:
+            event_pos = (event.xdata, event.ydata)
+            right_button = 3
+            self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
 
-        row += 1
+        pos_canvas = self.canvas.translate_coords(event_pos)
 
-        self.bottom_right_coordy_lbl = QtWidgets.QLabel('%s' % _('Bot Right Y'))
-        self.points_table.setCellWidget(row, 1, self.bottom_right_coordy_lbl)
-        self.bottom_right_coordy_tgt = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.bottom_right_coordy_tgt)
-        self.bottom_right_coordy_tgt.setReadOnly(True)
-        self.bottom_right_coordy_found = EvalEntry()
-        self.points_table.setCellWidget(row, 3, self.bottom_right_coordy_found)
-        row += 1
+        if event.button == 1:
+            click_pt = Point([pos_canvas[0], pos_canvas[1]])
 
-        # TOP LEFT
-        id_item_3 = QtWidgets.QTableWidgetItem('%d' % 3)
-        flags = QtCore.Qt.ItemIsEnabled
-        id_item_3.setFlags(flags)
-        self.points_table.setItem(row, 0, id_item_3)  # Tool name/id
+            if self.app.selection_type is not None:
+                # delete previous selection shape
+                self.app.delete_selection_shape()
+                self.app.selection_type = None
+            else:
+                if self.ui.cal_source_radio.get_value() == 'object':
+                    if self.target_obj.kind.lower() == 'excellon':
+                        for tool, tool_dict in self.target_obj.tools.items():
+                            for geo in tool_dict['solid_geometry']:
+                                if click_pt.within(geo):
+                                    center_pt = geo.centroid
+                                    self.click_points.append(
+                                        [
+                                            float('%.*f' % (self.decimals, center_pt.x)),
+                                            float('%.*f' % (self.decimals, center_pt.y))
+                                        ]
+                                    )
+                                    self.check_points()
+                    else:
+                        for apid, apid_val in self.target_obj.apertures.items():
+                            for geo_el in apid_val['geometry']:
+                                if 'solid' in geo_el:
+                                    if click_pt.within(geo_el['solid']):
+                                        if isinstance(geo_el['follow'], Point):
+                                            center_pt = geo_el['solid'].centroid
+                                            self.click_points.append(
+                                                [
+                                                    float('%.*f' % (self.decimals, center_pt.x)),
+                                                    float('%.*f' % (self.decimals, center_pt.y))
+                                                ]
+                                            )
+                                            self.check_points()
+                else:
+                    self.click_points.append(
+                        [
+                            float('%.*f' % (self.decimals, click_pt.x)),
+                            float('%.*f' % (self.decimals, click_pt.y))
+                        ]
+                    )
+                    self.check_points()
+        elif event.button == right_button and self.app.event_is_dragging is False:
+            if len(self.click_points) != 4:
+                self.reset_calibration_points()
+                self.disconnect_cal_events()
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request."))
 
-        self.top_left_coordx_lbl = QtWidgets.QLabel('%s' % _('Top Left X'))
-        self.points_table.setCellWidget(row, 1, self.top_left_coordx_lbl)
-        self.top_left_coordx_tgt = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.top_left_coordx_tgt)
-        self.top_left_coordx_tgt.setReadOnly(True)
-        self.top_left_coordx_found = EvalEntry()
-        self.points_table.setCellWidget(row, 3, self.top_left_coordx_found)
-        row += 1
+    def check_points(self):
+        if len(self.click_points) == 1:
+            self.ui.bottom_left_coordx_tgt.set_value(self.click_points[0][0])
+            self.ui.bottom_left_coordy_tgt.set_value(self.click_points[0][1])
+            self.app.inform.emit(_("Get Second calibration point. Bottom Right (Top Left)..."))
+        elif len(self.click_points) == 2:
+            self.ui.bottom_right_coordx_tgt.set_value(self.click_points[1][0])
+            self.ui.bottom_right_coordy_tgt.set_value(self.click_points[1][1])
+            self.app.inform.emit(_("Get Third calibration point. Top Left (Bottom Right)..."))
+        elif len(self.click_points) == 3:
+            self.ui.top_left_coordx_tgt.set_value(self.click_points[2][0])
+            self.ui.top_left_coordy_tgt.set_value(self.click_points[2][1])
+            self.app.inform.emit(_("Get Forth calibration point. Top Right..."))
+        elif len(self.click_points) == 4:
+            self.ui.top_right_coordx_tgt.set_value(self.click_points[3][0])
+            self.ui.top_right_coordy_tgt.set_value(self.click_points[3][1])
+            self.app.inform.emit('[success] %s' % _("Done. All four points have been acquired."))
+            self.disconnect_cal_events()
 
-        self.top_left_coordy_lbl = QtWidgets.QLabel('%s' % _('Top Left Y'))
-        self.points_table.setCellWidget(row, 1, self.top_left_coordy_lbl)
-        self.top_left_coordy_tgt = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.top_left_coordy_tgt)
-        self.top_left_coordy_tgt.setReadOnly(True)
-        self.top_left_coordy_found = EvalEntry()
-        self.points_table.setCellWidget(row, 3, self.top_left_coordy_found)
-        row += 1
+    def reset_calibration_points(self):
+        self.click_points = []
 
-        # TOP RIGHT
-        id_item_4 = QtWidgets.QTableWidgetItem('%d' % 4)
-        flags = QtCore.Qt.ItemIsEnabled
-        id_item_4.setFlags(flags)
-        self.points_table.setItem(row, 0, id_item_4)  # Tool name/id
+        self.ui.bottom_left_coordx_tgt.set_value('')
+        self.ui.bottom_left_coordy_tgt.set_value('')
 
-        self.top_right_coordx_lbl = QtWidgets.QLabel('%s' % _('Top Right X'))
-        self.points_table.setCellWidget(row, 1, self.top_right_coordx_lbl)
-        self.top_right_coordx_tgt = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.top_right_coordx_tgt)
-        self.top_right_coordx_tgt.setReadOnly(True)
-        self.top_right_coordx_found = EvalEntry()
-        self.top_right_coordx_found.setDisabled(True)
-        self.points_table.setCellWidget(row, 3, self.top_right_coordx_found)
-        row += 1
+        self.ui.bottom_right_coordx_tgt.set_value('')
+        self.ui.bottom_right_coordy_tgt.set_value('')
 
-        self.top_right_coordy_lbl = QtWidgets.QLabel('%s' % _('Top Right Y'))
-        self.points_table.setCellWidget(row, 1, self.top_right_coordy_lbl)
-        self.top_right_coordy_tgt = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.top_right_coordy_tgt)
-        self.top_right_coordy_tgt.setReadOnly(True)
-        self.top_right_coordy_found = EvalEntry()
-        self.top_right_coordy_found.setDisabled(True)
-        self.points_table.setCellWidget(row, 3, self.top_right_coordy_found)
+        self.ui.top_left_coordx_tgt.set_value('')
+        self.ui.top_left_coordy_tgt.set_value('')
 
-        vertical_header = self.points_table.verticalHeader()
-        vertical_header.hide()
-        self.points_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+        self.ui.top_right_coordx_tgt.set_value('')
+        self.ui.top_right_coordy_tgt.set_value('')
 
-        horizontal_header = self.points_table.horizontalHeader()
-        horizontal_header.setMinimumSectionSize(10)
-        horizontal_header.setDefaultSectionSize(70)
+        self.ui.bottom_right_coordx_found.set_value('')
+        self.ui.bottom_right_coordy_found.set_value('')
 
-        self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
-        # for x in range(4):
-        #     self.points_table.resizeColumnToContents(x)
-        self.points_table.resizeColumnsToContents()
-        self.points_table.resizeRowsToContents()
+        self.ui.top_left_coordx_found.set_value('')
+        self.ui.top_left_coordy_found.set_value('')
 
-        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(0, 20)
-        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
-        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
+    def gcode_header(self):
+        log.debug("ToolCalibration.gcode_header()")
+        time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
 
-        self.points_table.setMinimumHeight(self.points_table.getHeight() + 2)
-        self.points_table.setMaximumHeight(self.points_table.getHeight() + 3)
+        gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
+                (str(self.app.version), str(self.app.version_date)) + '\n'
 
-        # ## Get Points Button
-        self.start_button = QtWidgets.QPushButton(_("Get Points"))
-        self.start_button.setToolTip(
-            _("Pick four points by clicking on canvas if the source choice\n"
-              "is 'free' or inside the object geometry if the source is 'object'.\n"
-              "Those four points should be in the four squares of\n"
-              "the object.")
-        )
-        self.start_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay.addWidget(self.start_button, 17, 0, 1, 3)
+        gcode += '(Name: ' + _('Verification GCode for FlatCAM Calibration Tool') + ')\n'
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line, 18, 0, 1, 3)
+        gcode += '(Units: ' + self.units.upper() + ')\n\n'
+        gcode += '(Created on ' + time_str + ')\n\n'
+        gcode += 'G20\n' if self.units.upper() == 'IN' else 'G21\n'
+        gcode += 'G90\n'
+        gcode += 'G17\n'
+        gcode += 'G94\n\n'
+        return gcode
 
-        grid_lay.addWidget(QtWidgets.QLabel(''), 19, 0)
+    def close_tab(self):
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.tabText(idx) == _("Gcode Viewer"):
+                wdg = self.app.ui.plot_tab_area.widget(idx)
+                wdg.deleteLater()
+                self.app.ui.plot_tab_area.removeTab(idx)
 
-        # STEP 2 #
-        step_2 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 2: Verification GCode"))
-        step_2.setToolTip(
-            _("Generate GCode file to locate and align the PCB by using\n"
-              "the four points acquired above.\n"
-              "The points sequence is:\n"
-              "- first point -> set the origin\n"
-              "- second point -> alignment point. Can be: top-left or bottom-right.\n"
-              "- third point -> check point. Can be: top-left or bottom-right.\n"
-              "- forth point -> final verification point. Just for evaluation.")
-        )
-        grid_lay.addWidget(step_2, 20, 0, 1, 3)
+    def generate_verification_gcode(self):
+        sec_point = self.ui.second_point_radio.get_value()
 
-        # ## GCode Button
-        self.gcode_button = QtWidgets.QPushButton(_("Generate GCode"))
-        self.gcode_button.setToolTip(
-            _("Generate GCode file to locate and align the PCB by using\n"
-              "the four points acquired above.\n"
-              "The points sequence is:\n"
-              "- first point -> set the origin\n"
-              "- second point -> alignment point. Can be: top-left or bottom-right.\n"
-              "- third point -> check point. Can be: top-left or bottom-right.\n"
-              "- forth point -> final verification point. Just for evaluation.")
-        )
-        self.gcode_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay.addWidget(self.gcode_button, 21, 0, 1, 3)
+        travel_z = '%.*f' % (self.decimals, self.ui.travelz_entry.get_value())
+        toolchange_z = '%.*f' % (self.decimals, self.ui.toolchangez_entry.get_value())
+        toolchange_xy_temp = self.ui.toolchange_xy_entry.get_value().split(",")
+        toolchange_xy = [float(eval(a)) for a in toolchange_xy_temp if a != '']
 
-        separator_line1 = QtWidgets.QFrame()
-        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line1, 22, 0, 1, 3)
+        verification_z = '%.*f' % (self.decimals, self.ui.verz_entry.get_value())
 
-        grid_lay.addWidget(QtWidgets.QLabel(''), 23, 0, 1, 3)
+        if len(self.click_points) != 4:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. Four points are needed for GCode generation."))
+            return 'fail'
 
-        # STEP 3 #
-        step_3 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 3: Adjustments"))
-        step_3.setToolTip(
-            _("Calculate Scale and Skew factors based on the differences (delta)\n"
-              "found when checking the PCB pattern. The differences must be filled\n"
-              "in the fields Found (Delta).")
-        )
-        grid_lay.addWidget(step_3, 24, 0, 1, 3)
+        gcode = self.gcode_header()
+        if self.ui.zeroz_cb.get_value():
+            gcode += 'M5\n'
+            gcode += 'G00 Z%s\n' % toolchange_z
+            if toolchange_xy:
+                gcode += 'G00 X%s Y%s\n' % (toolchange_xy[0], toolchange_xy[1])
+            gcode += 'M0\n'
+            gcode += 'G01 Z0\n'
+            gcode += 'M0\n'
+            gcode += 'G00 Z%s\n' % toolchange_z
+            gcode += 'M0\n'
 
-        # ## Factors Button
-        self.generate_factors_button = QtWidgets.QPushButton(_("Calculate Factors"))
-        self.generate_factors_button.setToolTip(
-            _("Calculate Scale and Skew factors based on the differences (delta)\n"
-              "found when checking the PCB pattern. The differences must be filled\n"
-              "in the fields Found (Delta).")
-        )
-        self.generate_factors_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay.addWidget(self.generate_factors_button, 25, 0, 1, 3)
+        # first point: bottom - left -> ORIGIN set
+        gcode += 'G00 Z%s\n' % travel_z
+        gcode += 'G00 X%s Y%s\n' % (self.click_points[0][0], self.click_points[0][1])
+        gcode += 'G01 Z%s\n' % verification_z
+        gcode += 'M0\n'
 
-        separator_line1 = QtWidgets.QFrame()
-        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line1, 26, 0, 1, 3)
+        if sec_point == 'tl':
+            # second point: top - left -> align the PCB to this point
+            gcode += 'G00 Z%s\n' % travel_z
+            gcode += 'G00 X%s Y%s\n' % (self.click_points[2][0], self.click_points[2][1])
+            gcode += 'G01 Z%s\n' % verification_z
+            gcode += 'M0\n'
 
-        grid_lay.addWidget(QtWidgets.QLabel(''), 27, 0, 1, 3)
+            # third point: bottom - right -> check for scale on X axis or for skew on Y axis
+            gcode += 'G00 Z%s\n' % travel_z
+            gcode += 'G00 X%s Y%s\n' % (self.click_points[1][0], self.click_points[1][1])
+            gcode += 'G01 Z%s\n' % verification_z
+            gcode += 'M0\n'
 
-        # STEP 4 #
-        step_4 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 4: Adjusted GCode"))
-        step_4.setToolTip(
-            _("Generate verification GCode file adjusted with\n"
-              "the factors above.")
-        )
-        grid_lay.addWidget(step_4, 28, 0, 1, 3)
+            # forth point: top - right -> verification point
+            gcode += 'G00 Z%s\n' % travel_z
+            gcode += 'G00 X%s Y%s\n' % (self.click_points[3][0], self.click_points[3][1])
+            gcode += 'G01 Z%s\n' % verification_z
+            gcode += 'M0\n'
+        else:
+            # second point: bottom - right -> align the PCB to this point
+            gcode += 'G00 Z%s\n' % travel_z
+            gcode += 'G00 X%s Y%s\n' % (self.click_points[1][0], self.click_points[1][1])
+            gcode += 'G01 Z%s\n' % verification_z
+            gcode += 'M0\n'
 
-        self.scalex_label = QtWidgets.QLabel(_("Scale Factor X:"))
-        self.scalex_label.setToolTip(
-            _("Factor for Scale action over X axis.")
-        )
-        self.scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.scalex_entry.set_range(0, 9999.9999)
-        self.scalex_entry.set_precision(self.decimals)
-        self.scalex_entry.setSingleStep(0.1)
+            # third point: top - left -> check for scale on Y axis or for skew on X axis
+            gcode += 'G00 Z%s\n' % travel_z
+            gcode += 'G00 X%s Y%s\n' % (self.click_points[2][0], self.click_points[2][1])
+            gcode += 'G01 Z%s\n' % verification_z
+            gcode += 'M0\n'
 
-        grid_lay.addWidget(self.scalex_label, 29, 0)
-        grid_lay.addWidget(self.scalex_entry, 29, 1, 1, 2)
+            # forth point: top - right -> verification point
+            gcode += 'G00 Z%s\n' % travel_z
+            gcode += 'G00 X%s Y%s\n' % (self.click_points[3][0], self.click_points[3][1])
+            gcode += 'G01 Z%s\n' % verification_z
+            gcode += 'M0\n'
 
-        self.scaley_label = QtWidgets.QLabel(_("Scale Factor Y:"))
-        self.scaley_label.setToolTip(
-            _("Factor for Scale action over Y axis.")
-        )
-        self.scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.scaley_entry.set_range(0, 9999.9999)
-        self.scaley_entry.set_precision(self.decimals)
-        self.scaley_entry.setSingleStep(0.1)
+        # return to (toolchange_xy[0], toolchange_xy[1], toolchange_z) point for toolchange event
+        gcode += 'G00 Z%s\n' % travel_z
+        gcode += 'G00 X0 Y0\n'
+        gcode += 'G00 Z%s\n' % toolchange_z
+        if toolchange_xy:
+            gcode += 'G00 X%s Y%s\n' % (toolchange_xy[0], toolchange_xy[1])
 
-        grid_lay.addWidget(self.scaley_label, 30, 0)
-        grid_lay.addWidget(self.scaley_entry, 30, 1, 1, 2)
+        gcode += 'M2'
 
-        self.scale_button = QtWidgets.QPushButton(_("Apply Scale Factors"))
-        self.scale_button.setToolTip(
-            _("Apply Scale factors on the calibration points.")
-        )
-        self.scale_button.setStyleSheet("""
-                               QPushButton
-                               {
-                                   font-weight: bold;
-                               }
-                               """)
-        grid_lay.addWidget(self.scale_button, 31, 0, 1, 3)
+        self.gcode_editor_tab = AppTextEditor(app=self.app, plain_text=True)
 
-        self.skewx_label = QtWidgets.QLabel(_("Skew Angle X:"))
-        self.skewx_label.setToolTip(
-            _("Angle for Skew action, in degrees.\n"
-              "Float number between -360 and 359.")
-        )
-        self.skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.skewx_entry.set_range(-360, 360)
-        self.skewx_entry.set_precision(self.decimals)
-        self.skewx_entry.setSingleStep(0.1)
+        # add the tab if it was closed
+        self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Gcode Viewer"))
+        self.gcode_editor_tab.setObjectName('gcode_viewer_tab')
 
-        grid_lay.addWidget(self.skewx_label, 32, 0)
-        grid_lay.addWidget(self.skewx_entry, 32, 1, 1, 2)
+        # delete the absolute and relative position and messages in the infobar
+        self.app.ui.position_label.setText("")
+        self.app.ui.rel_position_label.setText("")
 
-        self.skewy_label = QtWidgets.QLabel(_("Skew Angle Y:"))
-        self.skewy_label.setToolTip(
-            _("Angle for Skew action, in degrees.\n"
-              "Float number between -360 and 359.")
-        )
-        self.skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.skewy_entry.set_range(-360, 360)
-        self.skewy_entry.set_precision(self.decimals)
-        self.skewy_entry.setSingleStep(0.1)
+        self.gcode_editor_tab.code_editor.completer_enable = False
+        self.gcode_editor_tab.buttonRun.hide()
 
-        grid_lay.addWidget(self.skewy_label, 33, 0)
-        grid_lay.addWidget(self.skewy_entry, 33, 1, 1, 2)
+        # Switch plot_area to CNCJob tab
+        self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab)
 
-        self.skew_button = QtWidgets.QPushButton(_("Apply Skew Factors"))
-        self.skew_button.setToolTip(
-            _("Apply Skew factors on the calibration points.")
-        )
-        self.skew_button.setStyleSheet("""
-                               QPushButton
-                               {
-                                   font-weight: bold;
-                               }
-                               """)
-        grid_lay.addWidget(self.skew_button, 34, 0, 1, 3)
+        self.gcode_editor_tab.t_frame.hide()
+        # then append the text from GCode to the text editor
+        try:
+            self.gcode_editor_tab.load_text(gcode, move_to_start=True, clear_text=True)
+        except Exception as e:
+            self.app.inform.emit('[ERROR] %s %s' % ('ERROR -->', str(e)))
+            return
+
+        self.gcode_editor_tab.t_frame.show()
+        self.app.proc_container.view.set_idle()
+
+        self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor'))
+
+        _filter_ = "G-Code Files (*.nc);;All Files (*.*)"
+        self.gcode_editor_tab.buttonSave.clicked.disconnect()
+        self.gcode_editor_tab.buttonSave.clicked.connect(
+            lambda: self.gcode_editor_tab.handleSaveGCode(name='fc_ver_gcode', filt=_filter_, callback=self.close_tab))
 
-        # final_factors_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Final Factors"))
-        # final_factors_lbl.setToolTip(
-        #     _("Generate verification GCode file adjusted with\n"
-        #       "the factors above.")
-        # )
-        # grid_lay.addWidget(final_factors_lbl, 27, 0, 1, 3)
-        #
-        # self.fin_scalex_label = QtWidgets.QLabel(_("Scale Factor X:"))
-        # self.fin_scalex_label.setToolTip(
-        #     _("Final factor for Scale action over X axis.")
-        # )
-        # self.fin_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        # self.fin_scalex_entry.set_range(0, 9999.9999)
-        # self.fin_scalex_entry.set_precision(self.decimals)
-        # self.fin_scalex_entry.setSingleStep(0.1)
-        #
-        # grid_lay.addWidget(self.fin_scalex_label, 28, 0)
-        # grid_lay.addWidget(self.fin_scalex_entry, 28, 1, 1, 2)
-        #
-        # self.fin_scaley_label = QtWidgets.QLabel(_("Scale Factor Y:"))
-        # self.fin_scaley_label.setToolTip(
-        #     _("Final factor for Scale action over Y axis.")
-        # )
-        # self.fin_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        # self.fin_scaley_entry.set_range(0, 9999.9999)
-        # self.fin_scaley_entry.set_precision(self.decimals)
-        # self.fin_scaley_entry.setSingleStep(0.1)
-        #
-        # grid_lay.addWidget(self.fin_scaley_label, 29, 0)
-        # grid_lay.addWidget(self.fin_scaley_entry, 29, 1, 1, 2)
-        #
-        # self.fin_skewx_label = QtWidgets.QLabel(_("Skew Angle X:"))
-        # self.fin_skewx_label.setToolTip(
-        #     _("Final value for angle for Skew action, in degrees.\n"
-        #       "Float number between -360 and 359.")
-        # )
-        # self.fin_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        # self.fin_skewx_entry.set_range(-360, 360)
-        # self.fin_skewx_entry.set_precision(self.decimals)
-        # self.fin_skewx_entry.setSingleStep(0.1)
-        #
-        # grid_lay.addWidget(self.fin_skewx_label, 30, 0)
-        # grid_lay.addWidget(self.fin_skewx_entry, 30, 1, 1, 2)
-        #
-        # self.fin_skewy_label = QtWidgets.QLabel(_("Skew Angle Y:"))
-        # self.fin_skewy_label.setToolTip(
-        #     _("Final value for angle for Skew action, in degrees.\n"
-        #       "Float number between -360 and 359.")
-        # )
-        # self.fin_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        # self.fin_skewy_entry.set_range(-360, 360)
-        # self.fin_skewy_entry.set_precision(self.decimals)
-        # self.fin_skewy_entry.setSingleStep(0.1)
-        #
-        # grid_lay.addWidget(self.fin_skewy_label, 31, 0)
-        # grid_lay.addWidget(self.fin_skewy_entry, 31, 1, 1, 2)
+    def calculate_factors(self):
+        origin_x = self.click_points[0][0]
+        origin_y = self.click_points[0][1]
 
-        # ## Adjusted GCode Button
+        top_left_x = self.click_points[2][0]
+        top_left_y = self.click_points[2][1]
 
-        self.adj_gcode_button = QtWidgets.QPushButton(_("Generate Adjusted GCode"))
-        self.adj_gcode_button.setToolTip(
-            _("Generate verification GCode file adjusted with\n"
-              "the factors set above.\n"
-              "The GCode parameters can be readjusted\n"
-              "before clicking this button.")
-        )
-        self.adj_gcode_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay.addWidget(self.adj_gcode_button, 42, 0, 1, 3)
+        bot_right_x = self.click_points[1][0]
+        bot_right_y = self.click_points[1][1]
 
-        separator_line1 = QtWidgets.QFrame()
-        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line1, 43, 0, 1, 3)
+        try:
+            top_left_dx = float(self.ui.top_left_coordx_found.get_value())
+        except TypeError:
+            top_left_dx = top_left_x
 
-        grid_lay.addWidget(QtWidgets.QLabel(''), 44, 0, 1, 3)
+        try:
+            top_left_dy = float(self.ui.top_left_coordy_found.get_value())
+        except TypeError:
+            top_left_dy = top_left_y
 
-        # STEP 5 #
-        step_5 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 5: Calibrate FlatCAM Objects"))
-        step_5.setToolTip(
-            _("Adjust the FlatCAM objects\n"
-              "with the factors determined and verified above.")
-        )
-        grid_lay.addWidget(step_5, 45, 0, 1, 3)
+        try:
+            bot_right_dx = float(self.ui.bottom_right_coordx_found.get_value())
+        except TypeError:
+            bot_right_dx = bot_right_x
 
-        self.adj_object_type_combo = FCComboBox()
-        self.adj_object_type_combo.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
+        try:
+            bot_right_dy = float(self.ui.bottom_right_coordy_found.get_value())
+        except TypeError:
+            bot_right_dy = bot_right_y
 
-        self.adj_object_type_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
-        self.adj_object_type_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
-        self.adj_object_type_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
+        # ------------------------------------------------------------------------------- #
+        # --------------------------- FACTORS CALCULUS ---------------------------------- #
+        # ------------------------------------------------------------------------------- #
+        if bot_right_dx != float('%.*f' % (self.decimals, bot_right_x)):
+            # we have scale on X
+            scale_x = (bot_right_dx / (bot_right_x - origin_x)) + 1
+            self.ui.scalex_entry.set_value(scale_x)
 
-        self.adj_object_type_label = QtWidgets.QLabel("%s:" % _("Adjusted object type"))
-        self.adj_object_type_label.setToolTip(_("Type of the FlatCAM Object to be adjusted."))
+        if top_left_dy != float('%.*f' % (self.decimals, top_left_y)):
+            # we have scale on Y
+            scale_y = (top_left_dy / (top_left_y - origin_y)) + 1
+            self.ui.scaley_entry.set_value(scale_y)
 
-        grid_lay.addWidget(self.adj_object_type_label, 46, 0, 1, 3)
-        grid_lay.addWidget(self.adj_object_type_combo, 47, 0, 1, 3)
+        if top_left_dx != float('%.*f' % (self.decimals, top_left_x)):
+            # we have skew on X
+            dx = top_left_dx
+            dy = top_left_y - origin_y
+            skew_angle_x = math.degrees(math.atan(dx / dy))
+            self.ui.skewx_entry.set_value(skew_angle_x)
 
-        self.adj_object_combo = FCComboBox()
-        self.adj_object_combo.setModel(self.app.collection)
-        self.adj_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.adj_object_combo.is_last = True
-        self.adj_object_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
-        }[self.adj_object_type_combo.get_value()]
+        if bot_right_dy != float('%.*f' % (self.decimals, bot_right_y)):
+            # we have skew on Y
+            dx = bot_right_x - origin_x
+            dy = bot_right_dy + origin_y
+            skew_angle_y = math.degrees(math.atan(dy / dx))
+            self.ui.skewy_entry.set_value(skew_angle_y)
 
-        self.adj_object_label = QtWidgets.QLabel("%s:" % _("Adjusted object selection"))
-        self.adj_object_label.setToolTip(
-            _("The FlatCAM Object to be adjusted.")
-        )
+    @property
+    def target_values_in_table(self):
+        self.click_points[0][0] = self.ui.bottom_left_coordx_tgt.get_value()
+        self.click_points[0][1] = self.ui.bottom_left_coordy_tgt.get_value()
 
-        grid_lay.addWidget(self.adj_object_label, 48, 0, 1, 3)
-        grid_lay.addWidget(self.adj_object_combo, 49, 0, 1, 3)
+        self.click_points[1][0] = self.ui.bottom_right_coordx_tgt.get_value()
+        self.click_points[1][1] = self.ui.bottom_right_coordy_tgt.get_value()
 
-        # ## Adjust Objects Button
-        self.cal_button = QtWidgets.QPushButton(_("Calibrate"))
-        self.cal_button.setToolTip(
-            _("Adjust (scale and/or skew) the objects\n"
-              "with the factors determined above.")
-        )
-        self.cal_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay.addWidget(self.cal_button, 50, 0, 1, 3)
+        self.click_points[2][0] = self.ui.top_left_coordx_tgt.get_value()
+        self.click_points[2][1] = self.ui.top_left_coordy_tgt.get_value()
 
-        separator_line2 = QtWidgets.QFrame()
-        separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line2, 51, 0, 1, 3)
+        self.click_points[3][0] = self.ui.top_right_coordx_tgt.get_value()
+        self.click_points[3][1] = self.ui.top_right_coordy_tgt.get_value()
 
-        grid_lay.addWidget(QtWidgets.QLabel(''), 52, 0, 1, 3)
+        return self.click_points
 
-        self.layout.addStretch()
+    @target_values_in_table.setter
+    def target_values_in_table(self, param):
+        bl_pt, br_pt, tl_pt, tr_pt = param
 
-        # ## 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.click_points[0] = [bl_pt[0], bl_pt[1]]
+        self.click_points[1] = [br_pt[0], br_pt[1]]
+        self.click_points[2] = [tl_pt[0], tl_pt[1]]
+        self.click_points[3] = [tr_pt[0], tr_pt[1]]
 
-        self.mr = None
-        self.units = ''
+        self.ui.bottom_left_coordx_tgt.set_value(float('%.*f' % (self.decimals, bl_pt[0])))
+        self.ui.bottom_left_coordy_tgt.set_value(float('%.*f' % (self.decimals, bl_pt[1])))
 
-        # here store 4 points to be used for calibration
-        self.click_points = [[], [], [], []]
+        self.ui.bottom_right_coordx_tgt.set_value(float('%.*f' % (self.decimals, br_pt[0])))
+        self.ui.bottom_right_coordy_tgt.set_value(float('%.*f' % (self.decimals, br_pt[1])))
 
-        # store the status of the grid
-        self.grid_status_memory = None
+        self.ui.top_left_coordx_tgt.set_value(float('%.*f' % (self.decimals, tl_pt[0])))
+        self.ui.top_left_coordy_tgt.set_value(float('%.*f' % (self.decimals, tl_pt[1])))
 
-        self.target_obj = None
+        self.ui.top_right_coordx_tgt.set_value(float('%.*f' % (self.decimals, tr_pt[0])))
+        self.ui.top_right_coordy_tgt.set_value(float('%.*f' % (self.decimals, tr_pt[1])))
 
-        # if the mouse events are connected to a local method set this True
-        self.local_connected = False
+    def on_scale_button(self):
+        scalex_fact = self.ui.scalex_entry.get_value()
+        scaley_fact = self.ui.scaley_entry.get_value()
+        bl, br, tl, tr = self.target_values_in_table
 
-        # reference for the tab where to open and view the verification GCode
-        self.gcode_editor_tab = None
+        bl_geo = Point(bl[0], bl[1])
+        br_geo = Point(br[0], br[1])
+        tl_geo = Point(tl[0], tl[1])
+        tr_geo = Point(tr[0], tr[1])
 
-        # calibrated object
-        self.cal_object = None
+        bl_scaled = scale(bl_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
+        br_scaled = scale(br_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
+        tl_scaled = scale(tl_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
+        tr_scaled = scale(tr_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
 
-        # ## Signals
-        self.cal_source_radio.activated_custom.connect(self.on_cal_source_radio)
-        self.obj_type_combo.currentIndexChanged.connect(self.on_obj_type_combo)
-        self.adj_object_type_combo.currentIndexChanged.connect(self.on_adj_obj_type_combo)
+        scaled_values = [
+            [bl_scaled.x, bl_scaled.y],
+            [br_scaled.x, br_scaled.y],
+            [tl_scaled.x, tl_scaled.y],
+            [tr_scaled.x, tr_scaled.y]
+        ]
+        self.target_values_in_table = scaled_values
 
-        self.start_button.clicked.connect(self.on_start_collect_points)
+    def on_skew_button(self):
+        skewx_angle = self.ui.skewx_entry.get_value()
+        skewy_angle = self.ui.skewy_entry.get_value()
+        bl, br, tl, tr = self.target_values_in_table
 
-        self.gcode_button.clicked.connect(self.generate_verification_gcode)
-        self.adj_gcode_button.clicked.connect(self.generate_verification_gcode)
+        bl_geo = Point(bl[0], bl[1])
+        br_geo = Point(br[0], br[1])
+        tl_geo = Point(tl[0], tl[1])
+        tr_geo = Point(tr[0], tr[1])
 
-        self.generate_factors_button.clicked.connect(self.calculate_factors)
+        bl_skewed = skew(bl_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
+        br_skewed = skew(br_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
+        tl_skewed = skew(tl_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
+        tr_skewed = skew(tr_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
 
-        self.scale_button.clicked.connect(self.on_scale_button)
-        self.skew_button.clicked.connect(self.on_skew_button)
+        skewed_values = [
+            [bl_skewed.x, bl_skewed.y],
+            [br_skewed.x, br_skewed.y],
+            [tl_skewed.x, tl_skewed.y],
+            [tr_skewed.x, tr_skewed.y]
+        ]
+        self.target_values_in_table = skewed_values
 
-        self.cal_button.clicked.connect(self.on_cal_button_click)
-        self.reset_button.clicked.connect(self.set_tool_ui)
+    def on_cal_button_click(self):
+        # get the FlatCAM object to calibrate
+        selection_index = self.ui.adj_object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.ui.adj_object_combo.rootModelIndex())
 
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolCalibration()")
+        try:
+            self.cal_object = model_index.internalPointer().obj
+        except Exception as e:
+            log.debug("ToolCalibration.on_cal_button_click() --> %s" % str(e))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no FlatCAM object selected..."))
+            return 'fail'
 
-        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])
+        obj_name = self.cal_object.options["name"] + "_calibrated"
+
+        self.app.worker_task.emit({'fcn': self.new_calibrated_object, 'params': [obj_name]})
+
+    def new_calibrated_object(self, obj_name):
+
+        try:
+            origin_x = self.click_points[0][0]
+            origin_y = self.click_points[0][1]
+        except IndexError as e:
+            log.debug("ToolCalibration.new_calibrated_object() --> %s" % str(e))
+            return 'fail'
 
-        AppTool.run(self)
+        scalex = self.ui.scalex_entry.get_value()
+        scaley = self.ui.scaley_entry.get_value()
 
-        self.set_tool_ui()
+        skewx = self.ui.skewx_entry.get_value()
+        skewy = self.ui.skewy_entry.get_value()
 
-        self.app.ui.notebook.setTabText(2, _("Calibration Tool"))
+        # create a new object adjusted (calibrated)
+        def initialize_geometry(obj_init, app):
+            obj_init.solid_geometry = deepcopy(obj.solid_geometry)
+            try:
+                obj_init.follow_geometry = deepcopy(obj.follow_geometry)
+            except AttributeError:
+                pass
 
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Alt+E', **kwargs)
+            try:
+                obj_init.apertures = deepcopy(obj.apertures)
+            except AttributeError:
+                pass
 
-    def set_tool_ui(self):
-        self.units = self.app.defaults['units'].upper()
+            try:
+                if obj.tools:
+                    obj_init.tools = deepcopy(obj.tools)
+            except Exception as ee:
+                log.debug("ToolCalibration.new_calibrated_object.initialize_geometry() --> %s" % str(ee))
 
-        if self.local_connected is True:
-            self.disconnect_cal_events()
+            obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
+            obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
 
-        self.bottom_left_coordx_found.set_value(_("Origin"))
-        self.bottom_left_coordy_found.set_value(_("Origin"))
+            try:
+                obj_init.source_file = deepcopy(obj.source_file)
+            except (AttributeError, TypeError):
+                pass
 
-        self.reset_calibration_points()
+        def initialize_gerber(obj_init, app):
+            obj_init.solid_geometry = deepcopy(obj.solid_geometry)
+            try:
+                obj_init.follow_geometry = deepcopy(obj.follow_geometry)
+            except AttributeError:
+                pass
 
-        self.cal_source_radio.set_value(self.app.defaults['tools_cal_calsource'])
-        self.travelz_entry.set_value(self.app.defaults['tools_cal_travelz'])
-        self.verz_entry.set_value(self.app.defaults['tools_cal_verz'])
-        self.zeroz_cb.set_value(self.app.defaults['tools_cal_zeroz'])
-        self.toolchangez_entry.set_value(self.app.defaults['tools_cal_toolchangez'])
-        self.toolchange_xy_entry.set_value(self.app.defaults['tools_cal_toolchange_xy'])
+            try:
+                obj_init.apertures = deepcopy(obj.apertures)
+            except AttributeError:
+                pass
 
-        self.second_point_radio.set_value(self.app.defaults['tools_cal_sec_point'])
+            try:
+                if obj.tools:
+                    obj_init.tools = deepcopy(obj.tools)
+            except Exception as err:
+                log.debug("ToolCalibration.new_calibrated_object.initialize_gerber() --> %s" % str(err))
 
-        self.scalex_entry.set_value(1.0)
-        self.scaley_entry.set_value(1.0)
-        self.skewx_entry.set_value(0.0)
-        self.skewy_entry.set_value(0.0)
+            obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
+            obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
 
-        # default object selection is Excellon = index_1
-        self.obj_type_combo.setCurrentIndex(1)
-        self.on_obj_type_combo()
+            try:
+                obj_init.source_file = self.export_gerber(obj_name=obj_name, filename=None, local_use=obj_init,
+                                                          use_thread=False)
+            except (AttributeError, TypeError):
+                pass
 
-        self.adj_object_type_combo.setCurrentIndex(0)
-        self.on_adj_obj_type_combo()
-        # self.adj_object_combo.setCurrentIndex(0)
+        def initialize_excellon(obj_init, app):
+            obj_init.tools = deepcopy(obj.tools)
 
-        # calibrated object
-        self.cal_object = None
+            # drills are offset, so they need to be deep copied
+            obj_init.drills = deepcopy(obj.drills)
+            # slots are offset, so they need to be deep copied
+            obj_init.slots = deepcopy(obj.slots)
 
-        self.app.inform.emit('%s...' % _("Tool initialized"))
+            obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
+            obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
 
-    def on_obj_type_combo(self):
-        obj_type = self.obj_type_combo.currentIndex()
-        self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        # self.object_combo.setCurrentIndex(0)
-        self.object_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Excellon"): "Excellon"
-        }[self.obj_type_combo.get_value()]
+            obj_init.create_geometry()
 
-    def on_adj_obj_type_combo(self):
-        obj_type = self.adj_object_type_combo.currentIndex()
-        self.adj_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        # self.adj_object_combo.setCurrentIndex(0)
-        self.adj_object_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
-        }[self.adj_object_type_combo.get_value()]
+            obj_init.source_file = self.app.export_excellon(obj_name=obj_name, local_use=obj, filename=None,
+                                                            use_thread=False)
 
-    def on_cal_source_radio(self, val):
-        if val == 'object':
-            self.obj_type_label.setDisabled(False)
-            self.obj_type_combo.setDisabled(False)
-            self.object_label.setDisabled(False)
-            self.object_combo.setDisabled(False)
-        else:
-            self.obj_type_label.setDisabled(True)
-            self.obj_type_combo.setDisabled(True)
-            self.object_label.setDisabled(True)
-            self.object_combo.setDisabled(True)
+        obj = self.cal_object
+        obj_name = obj_name
 
-    def on_start_collect_points(self):
+        if obj is None:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no FlatCAM object selected..."))
+            log.debug("ToolCalibration.new_calibrated_object() --> No object to calibrate")
+            return 'fail'
 
-        if self.cal_source_radio.get_value() == 'object':
-            selection_index = self.object_combo.currentIndex()
-            model_index = self.app.collection.index(selection_index, 0, self.object_combo.rootModelIndex())
-            try:
-                self.target_obj = model_index.internalPointer().obj
-            except AttributeError:
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no source FlatCAM object selected..."))
-                return
+        try:
+            if obj.kind.lower() == 'excellon':
+                self.app.app_obj.new_object("excellon", str(obj_name), initialize_excellon)
+            elif obj.kind.lower() == 'gerber':
+                self.app.app_obj.new_object("gerber", str(obj_name), initialize_gerber)
+            elif obj.kind.lower() == 'geometry':
+                self.app.app_obj.new_object("geometry", str(obj_name), initialize_geometry)
+        except Exception as e:
+            log.debug("ToolCalibration.new_calibrated_object() --> %s" % str(e))
+            return "Operation failed: %s" % str(e)
 
-        # disengage the grid snapping since it will be hard to find the drills on grid
-        if self.app.ui.grid_snap_btn.isChecked():
-            self.grid_status_memory = True
+    def disconnect_cal_events(self):
+        # restore the Grid snapping if it was active before
+        if self.grid_status_memory is True:
             self.app.ui.grid_snap_btn.trigger()
-        else:
-            self.grid_status_memory = False
 
-        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
+        self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
 
         if self.app.is_legacy is False:
-            self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
         else:
-            self.canvas.graph_event_disconnect(self.app.mr)
-
-        self.local_connected = True
-
-        self.reset_calibration_points()
+            self.canvas.graph_event_disconnect(self.mr)
 
-        self.app.inform.emit(_("Get First calibration point. Bottom Left..."))
+        self.local_connected = False
 
-    def on_mouse_click_release(self, event):
-        if self.app.is_legacy is False:
-            event_pos = event.pos
-            right_button = 2
-            self.app.event_is_dragging = self.app.event_is_dragging
-        else:
-            event_pos = (event.xdata, event.ydata)
-            right_button = 3
-            self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
+    def reset_fields(self):
+        self.ui.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.ui.adj_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
 
-        pos_canvas = self.canvas.translate_coords(event_pos)
 
-        if event.button == 1:
-            click_pt = Point([pos_canvas[0], pos_canvas[1]])
+class CalibUI:
 
-            if self.app.selection_type is not None:
-                # delete previous selection shape
-                self.app.delete_selection_shape()
-                self.app.selection_type = None
-            else:
-                if self.cal_source_radio.get_value() == 'object':
-                    if self.target_obj.kind.lower() == 'excellon':
-                        for tool, tool_dict in self.target_obj.tools.items():
-                            for geo in tool_dict['solid_geometry']:
-                                if click_pt.within(geo):
-                                    center_pt = geo.centroid
-                                    self.click_points.append(
-                                        [
-                                            float('%.*f' % (self.decimals, center_pt.x)),
-                                            float('%.*f' % (self.decimals, center_pt.y))
-                                        ]
-                                    )
-                                    self.check_points()
-                    else:
-                        for apid, apid_val in self.target_obj.apertures.items():
-                            for geo_el in apid_val['geometry']:
-                                if 'solid' in geo_el:
-                                    if click_pt.within(geo_el['solid']):
-                                        if isinstance(geo_el['follow'], Point):
-                                            center_pt = geo_el['solid'].centroid
-                                            self.click_points.append(
-                                                [
-                                                    float('%.*f' % (self.decimals, center_pt.x)),
-                                                    float('%.*f' % (self.decimals, center_pt.y))
-                                                ]
-                                            )
-                                            self.check_points()
-                else:
-                    self.click_points.append(
-                        [
-                            float('%.*f' % (self.decimals, click_pt.x)),
-                            float('%.*f' % (self.decimals, click_pt.y))
-                        ]
-                    )
-                    self.check_points()
-        elif event.button == right_button and self.app.event_is_dragging is False:
-            if len(self.click_points) != 4:
-                self.reset_calibration_points()
-                self.disconnect_cal_events()
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request."))
+    toolName = _("Calibration Tool")
 
-    def check_points(self):
-        if len(self.click_points) == 1:
-            self.bottom_left_coordx_tgt.set_value(self.click_points[0][0])
-            self.bottom_left_coordy_tgt.set_value(self.click_points[0][1])
-            self.app.inform.emit(_("Get Second calibration point. Bottom Right (Top Left)..."))
-        elif len(self.click_points) == 2:
-            self.bottom_right_coordx_tgt.set_value(self.click_points[1][0])
-            self.bottom_right_coordy_tgt.set_value(self.click_points[1][1])
-            self.app.inform.emit(_("Get Third calibration point. Top Left (Bottom Right)..."))
-        elif len(self.click_points) == 3:
-            self.top_left_coordx_tgt.set_value(self.click_points[2][0])
-            self.top_left_coordy_tgt.set_value(self.click_points[2][1])
-            self.app.inform.emit(_("Get Forth calibration point. Top Right..."))
-        elif len(self.click_points) == 4:
-            self.top_right_coordx_tgt.set_value(self.click_points[3][0])
-            self.top_right_coordy_tgt.set_value(self.click_points[3][1])
-            self.app.inform.emit('[success] %s' % _("Done. All four points have been acquired."))
-            self.disconnect_cal_events()
+    def __init__(self, layout, app):
+        self.app = app
+        self.decimals = self.app.decimals
+        self.layout = layout
 
-    def reset_calibration_points(self):
-        self.click_points = []
+        # ## Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                                QLabel
+                                {
+                                    font-size: 16px;
+                                    font-weight: bold;
+                                }
+                                """)
+        self.layout.addWidget(title_label)
 
-        self.bottom_left_coordx_tgt.set_value('')
-        self.bottom_left_coordy_tgt.set_value('')
+        self.layout.addWidget(QtWidgets.QLabel(""))
 
-        self.bottom_right_coordx_tgt.set_value('')
-        self.bottom_right_coordy_tgt.set_value('')
+        # ## Grid Layout
+        grid_lay = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay)
+        grid_lay.setColumnStretch(0, 0)
+        grid_lay.setColumnStretch(1, 1)
+        grid_lay.setColumnStretch(2, 0)
 
-        self.top_left_coordx_tgt.set_value('')
-        self.top_left_coordy_tgt.set_value('')
+        self.gcode_title_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
+        self.gcode_title_label.setToolTip(
+            _("Parameters used when creating the GCode in this tool.")
+        )
+        grid_lay.addWidget(self.gcode_title_label, 0, 0, 1, 3)
 
-        self.top_right_coordx_tgt.set_value('')
-        self.top_right_coordy_tgt.set_value('')
+        # Travel Z entry
+        travelz_lbl = QtWidgets.QLabel('%s:' % _("Travel Z"))
+        travelz_lbl.setToolTip(
+            _("Height (Z) for travelling between the points.")
+        )
 
-        self.bottom_right_coordx_found.set_value('')
-        self.bottom_right_coordy_found.set_value('')
+        self.travelz_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.travelz_entry.set_range(-9999.9999, 9999.9999)
+        self.travelz_entry.set_precision(self.decimals)
+        self.travelz_entry.setSingleStep(0.1)
 
-        self.top_left_coordx_found.set_value('')
-        self.top_left_coordy_found.set_value('')
+        grid_lay.addWidget(travelz_lbl, 1, 0)
+        grid_lay.addWidget(self.travelz_entry, 1, 1, 1, 2)
 
-    def gcode_header(self):
-        log.debug("ToolCalibration.gcode_header()")
-        time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
+        # Verification Z entry
+        verz_lbl = QtWidgets.QLabel('%s:' % _("Verification Z"))
+        verz_lbl.setToolTip(
+            _("Height (Z) for checking the point.")
+        )
 
-        gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
-                (str(self.app.version), str(self.app.version_date)) + '\n'
+        self.verz_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.verz_entry.set_range(-9999.9999, 9999.9999)
+        self.verz_entry.set_precision(self.decimals)
+        self.verz_entry.setSingleStep(0.1)
 
-        gcode += '(Name: ' + _('Verification GCode for FlatCAM Calibration Tool') + ')\n'
+        grid_lay.addWidget(verz_lbl, 2, 0)
+        grid_lay.addWidget(self.verz_entry, 2, 1, 1, 2)
 
-        gcode += '(Units: ' + self.units.upper() + ')\n\n'
-        gcode += '(Created on ' + time_str + ')\n\n'
-        gcode += 'G20\n' if self.units.upper() == 'IN' else 'G21\n'
-        gcode += 'G90\n'
-        gcode += 'G17\n'
-        gcode += 'G94\n\n'
-        return gcode
+        # Zero the Z of the verification tool
+        self.zeroz_cb = FCCheckBox('%s' % _("Zero Z tool"))
+        self.zeroz_cb.setToolTip(
+            _("Include a sequence to zero the height (Z)\n"
+              "of the verification tool.")
+        )
 
-    def close_tab(self):
-        for idx in range(self.app.ui.plot_tab_area.count()):
-            if self.app.ui.plot_tab_area.tabText(idx) == _("Gcode Viewer"):
-                wdg = self.app.ui.plot_tab_area.widget(idx)
-                wdg.deleteLater()
-                self.app.ui.plot_tab_area.removeTab(idx)
+        grid_lay.addWidget(self.zeroz_cb, 3, 0, 1, 3)
 
-    def generate_verification_gcode(self):
-        sec_point = self.second_point_radio.get_value()
+        # Toolchange Z entry
+        toolchangez_lbl = QtWidgets.QLabel('%s:' % _("Toolchange Z"))
+        toolchangez_lbl.setToolTip(
+            _("Height (Z) for mounting the verification probe.")
+        )
 
-        travel_z = '%.*f' % (self.decimals, self.travelz_entry.get_value())
-        toolchange_z = '%.*f' % (self.decimals, self.toolchangez_entry.get_value())
-        toolchange_xy_temp = self.toolchange_xy_entry.get_value().split(",")
-        toolchange_xy = [float(eval(a)) for a in toolchange_xy_temp if a != '']
+        self.toolchangez_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.toolchangez_entry.set_range(0.0000, 9999.9999)
+        self.toolchangez_entry.set_precision(self.decimals)
+        self.toolchangez_entry.setSingleStep(0.1)
 
-        verification_z = '%.*f' % (self.decimals, self.verz_entry.get_value())
+        grid_lay.addWidget(toolchangez_lbl, 4, 0)
+        grid_lay.addWidget(self.toolchangez_entry, 4, 1, 1, 2)
 
-        if len(self.click_points) != 4:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. Four points are needed for GCode generation."))
-            return 'fail'
+        # Toolchange X-Y entry
+        toolchangexy_lbl = QtWidgets.QLabel('%s:' % _('Toolchange X-Y'))
+        toolchangexy_lbl.setToolTip(
+            _("Toolchange X,Y position.\n"
+              "If no value is entered then the current\n"
+              "(x, y) point will be used,")
+        )
 
-        gcode = self.gcode_header()
-        if self.zeroz_cb.get_value():
-            gcode += 'M5\n'
-            gcode += 'G00 Z%s\n' % toolchange_z
-            if toolchange_xy:
-                gcode += 'G00 X%s Y%s\n' % (toolchange_xy[0], toolchange_xy[1])
-            gcode += 'M0\n'
-            gcode += 'G01 Z0\n'
-            gcode += 'M0\n'
-            gcode += 'G00 Z%s\n' % toolchange_z
-            gcode += 'M0\n'
+        self.toolchange_xy_entry = FCEntry()
 
-        # first point: bottom - left -> ORIGIN set
-        gcode += 'G00 Z%s\n' % travel_z
-        gcode += 'G00 X%s Y%s\n' % (self.click_points[0][0], self.click_points[0][1])
-        gcode += 'G01 Z%s\n' % verification_z
-        gcode += 'M0\n'
+        grid_lay.addWidget(toolchangexy_lbl, 5, 0)
+        grid_lay.addWidget(self.toolchange_xy_entry, 5, 1, 1, 2)
 
-        if sec_point == 'tl':
-            # second point: top - left -> align the PCB to this point
-            gcode += 'G00 Z%s\n' % travel_z
-            gcode += 'G00 X%s Y%s\n' % (self.click_points[2][0], self.click_points[2][1])
-            gcode += 'G01 Z%s\n' % verification_z
-            gcode += 'M0\n'
+        self.z_ois = OptionalInputSection(
+            self.zeroz_cb,
+            [
+                toolchangez_lbl,
+                self.toolchangez_entry,
+                toolchangexy_lbl,
+                self.toolchange_xy_entry
+            ]
+        )
 
-            # third point: bottom - right -> check for scale on X axis or for skew on Y axis
-            gcode += 'G00 Z%s\n' % travel_z
-            gcode += 'G00 X%s Y%s\n' % (self.click_points[1][0], self.click_points[1][1])
-            gcode += 'G01 Z%s\n' % verification_z
-            gcode += 'M0\n'
+        separator_line1 = QtWidgets.QFrame()
+        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line1, 6, 0, 1, 3)
 
-            # forth point: top - right -> verification point
-            gcode += 'G00 Z%s\n' % travel_z
-            gcode += 'G00 X%s Y%s\n' % (self.click_points[3][0], self.click_points[3][1])
-            gcode += 'G01 Z%s\n' % verification_z
-            gcode += 'M0\n'
-        else:
-            # second point: bottom - right -> align the PCB to this point
-            gcode += 'G00 Z%s\n' % travel_z
-            gcode += 'G00 X%s Y%s\n' % (self.click_points[1][0], self.click_points[1][1])
-            gcode += 'G01 Z%s\n' % verification_z
-            gcode += 'M0\n'
+        # Second point choice
+        second_point_lbl = QtWidgets.QLabel('%s:' % _("Second point"))
+        second_point_lbl.setToolTip(
+            _("Second point in the Gcode verification can be:\n"
+              "- top-left -> the user will align the PCB vertically\n"
+              "- bottom-right -> the user will align the PCB horizontally")
+        )
+        self.second_point_radio = RadioSet([{'label': _('Top-Left'), 'value': 'tl'},
+                                            {'label': _('Bottom-Right'), 'value': 'br'}],
+                                           orientation='vertical')
 
-            # third point: top - left -> check for scale on Y axis or for skew on X axis
-            gcode += 'G00 Z%s\n' % travel_z
-            gcode += 'G00 X%s Y%s\n' % (self.click_points[2][0], self.click_points[2][1])
-            gcode += 'G01 Z%s\n' % verification_z
-            gcode += 'M0\n'
+        grid_lay.addWidget(second_point_lbl, 7, 0)
+        grid_lay.addWidget(self.second_point_radio, 7, 1, 1, 2)
 
-            # forth point: top - right -> verification point
-            gcode += 'G00 Z%s\n' % travel_z
-            gcode += 'G00 X%s Y%s\n' % (self.click_points[3][0], self.click_points[3][1])
-            gcode += 'G01 Z%s\n' % verification_z
-            gcode += 'M0\n'
+        separator_line1 = QtWidgets.QFrame()
+        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line1, 8, 0, 1, 3)
 
-        # return to (toolchange_xy[0], toolchange_xy[1], toolchange_z) point for toolchange event
-        gcode += 'G00 Z%s\n' % travel_z
-        gcode += 'G00 X0 Y0\n'
-        gcode += 'G00 Z%s\n' % toolchange_z
-        if toolchange_xy:
-            gcode += 'G00 X%s Y%s\n' % (toolchange_xy[0], toolchange_xy[1])
+        grid_lay.addWidget(QtWidgets.QLabel(''), 9, 0, 1, 3)
 
-        gcode += 'M2'
+        step_1 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 1: Acquire Calibration Points"))
+        step_1.setToolTip(
+            _("Pick four points by clicking on canvas.\n"
+              "Those four points should be in the four\n"
+              "(as much as possible) corners of the object.")
+        )
+        grid_lay.addWidget(step_1, 10, 0, 1, 3)
 
-        self.gcode_editor_tab = AppTextEditor(app=self.app, plain_text=True)
+        self.cal_source_lbl = QtWidgets.QLabel("<b>%s:</b>" % _("Source Type"))
+        self.cal_source_lbl.setToolTip(_("The source of calibration points.\n"
+                                         "It can be:\n"
+                                         "- Object -> click a hole geo for Excellon or a pad for Gerber\n"
+                                         "- Free -> click freely on canvas to acquire the calibration points"))
+        self.cal_source_radio = RadioSet([{'label': _('Object'), 'value': 'object'},
+                                          {'label': _('Free'), 'value': 'free'}],
+                                         stretch=False)
 
-        # add the tab if it was closed
-        self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Gcode Viewer"))
-        self.gcode_editor_tab.setObjectName('gcode_viewer_tab')
+        grid_lay.addWidget(self.cal_source_lbl, 11, 0)
+        grid_lay.addWidget(self.cal_source_radio, 11, 1, 1, 2)
 
-        # delete the absolute and relative position and messages in the infobar
-        self.app.ui.position_label.setText("")
-        self.app.ui.rel_position_label.setText("")
+        self.obj_type_label = QtWidgets.QLabel("%s:" % _("Object Type"))
 
-        self.gcode_editor_tab.code_editor.completer_enable = False
-        self.gcode_editor_tab.buttonRun.hide()
+        self.obj_type_combo = FCComboBox()
+        self.obj_type_combo.addItem(_("Gerber"))
+        self.obj_type_combo.addItem(_("Excellon"))
 
-        # Switch plot_area to CNCJob tab
-        self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab)
+        self.obj_type_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
+        self.obj_type_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
 
-        self.gcode_editor_tab.t_frame.hide()
-        # then append the text from GCode to the text editor
-        try:
-            self.gcode_editor_tab.load_text(gcode, move_to_start=True, clear_text=True)
-        except Exception as e:
-            self.app.inform.emit('[ERROR] %s %s' % ('ERROR -->', str(e)))
-            return
+        grid_lay.addWidget(self.obj_type_label, 12, 0)
+        grid_lay.addWidget(self.obj_type_combo, 12, 1, 1, 2)
 
-        self.gcode_editor_tab.t_frame.show()
-        self.app.proc_container.view.set_idle()
+        self.object_combo = FCComboBox()
+        self.object_combo.setModel(self.app.collection)
+        self.object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
+        self.object_combo.is_last = True
 
-        self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor'))
+        self.object_label = QtWidgets.QLabel("%s:" % _("Source object selection"))
+        self.object_label.setToolTip(
+            _("FlatCAM Object to be used as a source for reference points.")
+        )
 
-        _filter_ = "G-Code Files (*.nc);;All Files (*.*)"
-        self.gcode_editor_tab.buttonSave.clicked.disconnect()
-        self.gcode_editor_tab.buttonSave.clicked.connect(
-            lambda: self.gcode_editor_tab.handleSaveGCode(name='fc_ver_gcode', filt=_filter_, callback=self.close_tab))
+        grid_lay.addWidget(self.object_label, 13, 0, 1, 3)
+        grid_lay.addWidget(self.object_combo, 14, 0, 1, 3)
 
-    def calculate_factors(self):
-        origin_x = self.click_points[0][0]
-        origin_y = self.click_points[0][1]
+        self.points_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Calibration Points'))
+        self.points_table_label.setToolTip(
+            _("Contain the expected calibration points and the\n"
+              "ones measured.")
+        )
+        grid_lay.addWidget(self.points_table_label, 15, 0, 1, 3)
 
-        top_left_x = self.click_points[2][0]
-        top_left_y = self.click_points[2][1]
+        self.points_table = FCTable()
+        self.points_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+        # self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
+        grid_lay.addWidget(self.points_table, 16, 0, 1, 3)
 
-        bot_right_x = self.click_points[1][0]
-        bot_right_y = self.click_points[1][1]
+        self.points_table.setColumnCount(4)
+        self.points_table.setHorizontalHeaderLabels(
+            [
+                '#',
+                _("Name"),
+                _("Target"),
+                _("Found Delta")
+            ]
+        )
+        self.points_table.setRowCount(8)
+        row = 0
 
-        try:
-            top_left_dx = float(self.top_left_coordx_found.get_value())
-        except TypeError:
-            top_left_dx = top_left_x
+        # BOTTOM LEFT
+        id_item_1 = QtWidgets.QTableWidgetItem('%d' % 1)
+        flags = QtCore.Qt.ItemIsEnabled
+        id_item_1.setFlags(flags)
+        self.points_table.setItem(row, 0, id_item_1)  # Tool name/id
 
-        try:
-            top_left_dy = float(self.top_left_coordy_found.get_value())
-        except TypeError:
-            top_left_dy = top_left_y
+        self.bottom_left_coordx_lbl = QtWidgets.QLabel('%s' % _('Bot Left X'))
+        self.points_table.setCellWidget(row, 1, self.bottom_left_coordx_lbl)
+        self.bottom_left_coordx_tgt = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.bottom_left_coordx_tgt)
+        self.bottom_left_coordx_tgt.setReadOnly(True)
+        self.bottom_left_coordx_found = EvalEntry()
+        self.points_table.setCellWidget(row, 3, self.bottom_left_coordx_found)
+        row += 1
 
-        try:
-            bot_right_dx = float(self.bottom_right_coordx_found.get_value())
-        except TypeError:
-            bot_right_dx = bot_right_x
+        self.bottom_left_coordy_lbl = QtWidgets.QLabel('%s' % _('Bot Left Y'))
+        self.points_table.setCellWidget(row, 1, self.bottom_left_coordy_lbl)
+        self.bottom_left_coordy_tgt = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.bottom_left_coordy_tgt)
+        self.bottom_left_coordy_tgt.setReadOnly(True)
+        self.bottom_left_coordy_found = EvalEntry()
+        self.points_table.setCellWidget(row, 3, self.bottom_left_coordy_found)
 
-        try:
-            bot_right_dy = float(self.bottom_right_coordy_found.get_value())
-        except TypeError:
-            bot_right_dy = bot_right_y
+        self.bottom_left_coordx_found.setDisabled(True)
+        self.bottom_left_coordy_found.setDisabled(True)
+        row += 1
 
-        # ------------------------------------------------------------------------------- #
-        # --------------------------- FACTORS CALCULUS ---------------------------------- #
-        # ------------------------------------------------------------------------------- #
-        if bot_right_dx != float('%.*f' % (self.decimals, bot_right_x)):
-            # we have scale on X
-            scale_x = (bot_right_dx / (bot_right_x - origin_x)) + 1
-            self.scalex_entry.set_value(scale_x)
+        # BOTTOM RIGHT
+        id_item_2 = QtWidgets.QTableWidgetItem('%d' % 2)
+        flags = QtCore.Qt.ItemIsEnabled
+        id_item_2.setFlags(flags)
+        self.points_table.setItem(row, 0, id_item_2)  # Tool name/id
+
+        self.bottom_right_coordx_lbl = QtWidgets.QLabel('%s' % _('Bot Right X'))
+        self.points_table.setCellWidget(row, 1, self.bottom_right_coordx_lbl)
+        self.bottom_right_coordx_tgt = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.bottom_right_coordx_tgt)
+        self.bottom_right_coordx_tgt.setReadOnly(True)
+        self.bottom_right_coordx_found = EvalEntry()
+        self.points_table.setCellWidget(row, 3, self.bottom_right_coordx_found)
 
-        if top_left_dy != float('%.*f' % (self.decimals, top_left_y)):
-            # we have scale on Y
-            scale_y = (top_left_dy / (top_left_y - origin_y)) + 1
-            self.scaley_entry.set_value(scale_y)
+        row += 1
 
-        if top_left_dx != float('%.*f' % (self.decimals, top_left_x)):
-            # we have skew on X
-            dx = top_left_dx
-            dy = top_left_y - origin_y
-            skew_angle_x = math.degrees(math.atan(dx / dy))
-            self.skewx_entry.set_value(skew_angle_x)
+        self.bottom_right_coordy_lbl = QtWidgets.QLabel('%s' % _('Bot Right Y'))
+        self.points_table.setCellWidget(row, 1, self.bottom_right_coordy_lbl)
+        self.bottom_right_coordy_tgt = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.bottom_right_coordy_tgt)
+        self.bottom_right_coordy_tgt.setReadOnly(True)
+        self.bottom_right_coordy_found = EvalEntry()
+        self.points_table.setCellWidget(row, 3, self.bottom_right_coordy_found)
+        row += 1
 
-        if bot_right_dy != float('%.*f' % (self.decimals, bot_right_y)):
-            # we have skew on Y
-            dx = bot_right_x - origin_x
-            dy = bot_right_dy + origin_y
-            skew_angle_y = math.degrees(math.atan(dy / dx))
-            self.skewy_entry.set_value(skew_angle_y)
+        # TOP LEFT
+        id_item_3 = QtWidgets.QTableWidgetItem('%d' % 3)
+        flags = QtCore.Qt.ItemIsEnabled
+        id_item_3.setFlags(flags)
+        self.points_table.setItem(row, 0, id_item_3)  # Tool name/id
 
-    @property
-    def target_values_in_table(self):
-        self.click_points[0][0] = self.bottom_left_coordx_tgt.get_value()
-        self.click_points[0][1] = self.bottom_left_coordy_tgt.get_value()
+        self.top_left_coordx_lbl = QtWidgets.QLabel('%s' % _('Top Left X'))
+        self.points_table.setCellWidget(row, 1, self.top_left_coordx_lbl)
+        self.top_left_coordx_tgt = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.top_left_coordx_tgt)
+        self.top_left_coordx_tgt.setReadOnly(True)
+        self.top_left_coordx_found = EvalEntry()
+        self.points_table.setCellWidget(row, 3, self.top_left_coordx_found)
+        row += 1
 
-        self.click_points[1][0] = self.bottom_right_coordx_tgt.get_value()
-        self.click_points[1][1] = self.bottom_right_coordy_tgt.get_value()
+        self.top_left_coordy_lbl = QtWidgets.QLabel('%s' % _('Top Left Y'))
+        self.points_table.setCellWidget(row, 1, self.top_left_coordy_lbl)
+        self.top_left_coordy_tgt = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.top_left_coordy_tgt)
+        self.top_left_coordy_tgt.setReadOnly(True)
+        self.top_left_coordy_found = EvalEntry()
+        self.points_table.setCellWidget(row, 3, self.top_left_coordy_found)
+        row += 1
 
-        self.click_points[2][0] = self.top_left_coordx_tgt.get_value()
-        self.click_points[2][1] = self.top_left_coordy_tgt.get_value()
+        # TOP RIGHT
+        id_item_4 = QtWidgets.QTableWidgetItem('%d' % 4)
+        flags = QtCore.Qt.ItemIsEnabled
+        id_item_4.setFlags(flags)
+        self.points_table.setItem(row, 0, id_item_4)  # Tool name/id
 
-        self.click_points[3][0] = self.top_right_coordx_tgt.get_value()
-        self.click_points[3][1] = self.top_right_coordy_tgt.get_value()
+        self.top_right_coordx_lbl = QtWidgets.QLabel('%s' % _('Top Right X'))
+        self.points_table.setCellWidget(row, 1, self.top_right_coordx_lbl)
+        self.top_right_coordx_tgt = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.top_right_coordx_tgt)
+        self.top_right_coordx_tgt.setReadOnly(True)
+        self.top_right_coordx_found = EvalEntry()
+        self.top_right_coordx_found.setDisabled(True)
+        self.points_table.setCellWidget(row, 3, self.top_right_coordx_found)
+        row += 1
 
-        return self.click_points
+        self.top_right_coordy_lbl = QtWidgets.QLabel('%s' % _('Top Right Y'))
+        self.points_table.setCellWidget(row, 1, self.top_right_coordy_lbl)
+        self.top_right_coordy_tgt = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.top_right_coordy_tgt)
+        self.top_right_coordy_tgt.setReadOnly(True)
+        self.top_right_coordy_found = EvalEntry()
+        self.top_right_coordy_found.setDisabled(True)
+        self.points_table.setCellWidget(row, 3, self.top_right_coordy_found)
 
-    @target_values_in_table.setter
-    def target_values_in_table(self, param):
-        bl_pt, br_pt, tl_pt, tr_pt = param
+        vertical_header = self.points_table.verticalHeader()
+        vertical_header.hide()
+        self.points_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
 
-        self.click_points[0] = [bl_pt[0], bl_pt[1]]
-        self.click_points[1] = [br_pt[0], br_pt[1]]
-        self.click_points[2] = [tl_pt[0], tl_pt[1]]
-        self.click_points[3] = [tr_pt[0], tr_pt[1]]
+        horizontal_header = self.points_table.horizontalHeader()
+        horizontal_header.setMinimumSectionSize(10)
+        horizontal_header.setDefaultSectionSize(70)
 
-        self.bottom_left_coordx_tgt.set_value(float('%.*f' % (self.decimals, bl_pt[0])))
-        self.bottom_left_coordy_tgt.set_value(float('%.*f' % (self.decimals, bl_pt[1])))
+        self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
+        # for x in range(4):
+        #     self.points_table.resizeColumnToContents(x)
+        self.points_table.resizeColumnsToContents()
+        self.points_table.resizeRowsToContents()
 
-        self.bottom_right_coordx_tgt.set_value(float('%.*f' % (self.decimals, br_pt[0])))
-        self.bottom_right_coordy_tgt.set_value(float('%.*f' % (self.decimals, br_pt[1])))
+        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(0, 20)
+        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
+        horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
 
-        self.top_left_coordx_tgt.set_value(float('%.*f' % (self.decimals, tl_pt[0])))
-        self.top_left_coordy_tgt.set_value(float('%.*f' % (self.decimals, tl_pt[1])))
+        self.points_table.setMinimumHeight(self.points_table.getHeight() + 2)
+        self.points_table.setMaximumHeight(self.points_table.getHeight() + 3)
 
-        self.top_right_coordx_tgt.set_value(float('%.*f' % (self.decimals, tr_pt[0])))
-        self.top_right_coordy_tgt.set_value(float('%.*f' % (self.decimals, tr_pt[1])))
+        # ## Get Points Button
+        self.start_button = QtWidgets.QPushButton(_("Get Points"))
+        self.start_button.setToolTip(
+            _("Pick four points by clicking on canvas if the source choice\n"
+              "is 'free' or inside the object geometry if the source is 'object'.\n"
+              "Those four points should be in the four squares of\n"
+              "the object.")
+        )
+        self.start_button.setStyleSheet("""
+                               QPushButton
+                               {
+                                   font-weight: bold;
+                               }
+                               """)
+        grid_lay.addWidget(self.start_button, 17, 0, 1, 3)
 
-    def on_scale_button(self):
-        scalex_fact = self.scalex_entry.get_value()
-        scaley_fact = self.scaley_entry.get_value()
-        bl, br, tl, tr = self.target_values_in_table
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line, 18, 0, 1, 3)
 
-        bl_geo = Point(bl[0], bl[1])
-        br_geo = Point(br[0], br[1])
-        tl_geo = Point(tl[0], tl[1])
-        tr_geo = Point(tr[0], tr[1])
+        grid_lay.addWidget(QtWidgets.QLabel(''), 19, 0)
 
-        bl_scaled = scale(bl_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
-        br_scaled = scale(br_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
-        tl_scaled = scale(tl_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
-        tr_scaled = scale(tr_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
+        # STEP 2 #
+        step_2 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 2: Verification GCode"))
+        step_2.setToolTip(
+            _("Generate GCode file to locate and align the PCB by using\n"
+              "the four points acquired above.\n"
+              "The points sequence is:\n"
+              "- first point -> set the origin\n"
+              "- second point -> alignment point. Can be: top-left or bottom-right.\n"
+              "- third point -> check point. Can be: top-left or bottom-right.\n"
+              "- forth point -> final verification point. Just for evaluation.")
+        )
+        grid_lay.addWidget(step_2, 20, 0, 1, 3)
 
-        scaled_values = [
-            [bl_scaled.x, bl_scaled.y],
-            [br_scaled.x, br_scaled.y],
-            [tl_scaled.x, tl_scaled.y],
-            [tr_scaled.x, tr_scaled.y]
-        ]
-        self.target_values_in_table = scaled_values
+        # ## GCode Button
+        self.gcode_button = QtWidgets.QPushButton(_("Generate GCode"))
+        self.gcode_button.setToolTip(
+            _("Generate GCode file to locate and align the PCB by using\n"
+              "the four points acquired above.\n"
+              "The points sequence is:\n"
+              "- first point -> set the origin\n"
+              "- second point -> alignment point. Can be: top-left or bottom-right.\n"
+              "- third point -> check point. Can be: top-left or bottom-right.\n"
+              "- forth point -> final verification point. Just for evaluation.")
+        )
+        self.gcode_button.setStyleSheet("""
+                               QPushButton
+                               {
+                                   font-weight: bold;
+                               }
+                               """)
+        grid_lay.addWidget(self.gcode_button, 21, 0, 1, 3)
 
-    def on_skew_button(self):
-        skewx_angle = self.skewx_entry.get_value()
-        skewy_angle = self.skewy_entry.get_value()
-        bl, br, tl, tr = self.target_values_in_table
+        separator_line1 = QtWidgets.QFrame()
+        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line1, 22, 0, 1, 3)
 
-        bl_geo = Point(bl[0], bl[1])
-        br_geo = Point(br[0], br[1])
-        tl_geo = Point(tl[0], tl[1])
-        tr_geo = Point(tr[0], tr[1])
+        grid_lay.addWidget(QtWidgets.QLabel(''), 23, 0, 1, 3)
 
-        bl_skewed = skew(bl_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
-        br_skewed = skew(br_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
-        tl_skewed = skew(tl_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
-        tr_skewed = skew(tr_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
+        # STEP 3 #
+        step_3 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 3: Adjustments"))
+        step_3.setToolTip(
+            _("Calculate Scale and Skew factors based on the differences (delta)\n"
+              "found when checking the PCB pattern. The differences must be filled\n"
+              "in the fields Found (Delta).")
+        )
+        grid_lay.addWidget(step_3, 24, 0, 1, 3)
 
-        skewed_values = [
-            [bl_skewed.x, bl_skewed.y],
-            [br_skewed.x, br_skewed.y],
-            [tl_skewed.x, tl_skewed.y],
-            [tr_skewed.x, tr_skewed.y]
-        ]
-        self.target_values_in_table = skewed_values
+        # ## Factors Button
+        self.generate_factors_button = QtWidgets.QPushButton(_("Calculate Factors"))
+        self.generate_factors_button.setToolTip(
+            _("Calculate Scale and Skew factors based on the differences (delta)\n"
+              "found when checking the PCB pattern. The differences must be filled\n"
+              "in the fields Found (Delta).")
+        )
+        self.generate_factors_button.setStyleSheet("""
+                               QPushButton
+                               {
+                                   font-weight: bold;
+                               }
+                               """)
+        grid_lay.addWidget(self.generate_factors_button, 25, 0, 1, 3)
 
-    def on_cal_button_click(self):
-        # get the FlatCAM object to calibrate
-        selection_index = self.adj_object_combo.currentIndex()
-        model_index = self.app.collection.index(selection_index, 0, self.adj_object_combo.rootModelIndex())
+        separator_line1 = QtWidgets.QFrame()
+        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line1, 26, 0, 1, 3)
 
-        try:
-            self.cal_object = model_index.internalPointer().obj
-        except Exception as e:
-            log.debug("ToolCalibration.on_cal_button_click() --> %s" % str(e))
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no FlatCAM object selected..."))
-            return 'fail'
+        grid_lay.addWidget(QtWidgets.QLabel(''), 27, 0, 1, 3)
 
-        obj_name = self.cal_object.options["name"] + "_calibrated"
+        # STEP 4 #
+        step_4 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 4: Adjusted GCode"))
+        step_4.setToolTip(
+            _("Generate verification GCode file adjusted with\n"
+              "the factors above.")
+        )
+        grid_lay.addWidget(step_4, 28, 0, 1, 3)
 
-        self.app.worker_task.emit({'fcn': self.new_calibrated_object, 'params': [obj_name]})
+        self.scalex_label = QtWidgets.QLabel(_("Scale Factor X:"))
+        self.scalex_label.setToolTip(
+            _("Factor for Scale action over X axis.")
+        )
+        self.scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.scalex_entry.set_range(0, 9999.9999)
+        self.scalex_entry.set_precision(self.decimals)
+        self.scalex_entry.setSingleStep(0.1)
 
-    def new_calibrated_object(self, obj_name):
+        grid_lay.addWidget(self.scalex_label, 29, 0)
+        grid_lay.addWidget(self.scalex_entry, 29, 1, 1, 2)
 
-        try:
-            origin_x = self.click_points[0][0]
-            origin_y = self.click_points[0][1]
-        except IndexError as e:
-            log.debug("ToolCalibration.new_calibrated_object() --> %s" % str(e))
-            return 'fail'
+        self.scaley_label = QtWidgets.QLabel(_("Scale Factor Y:"))
+        self.scaley_label.setToolTip(
+            _("Factor for Scale action over Y axis.")
+        )
+        self.scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.scaley_entry.set_range(0, 9999.9999)
+        self.scaley_entry.set_precision(self.decimals)
+        self.scaley_entry.setSingleStep(0.1)
 
-        scalex = self.scalex_entry.get_value()
-        scaley = self.scaley_entry.get_value()
+        grid_lay.addWidget(self.scaley_label, 30, 0)
+        grid_lay.addWidget(self.scaley_entry, 30, 1, 1, 2)
 
-        skewx = self.skewx_entry.get_value()
-        skewy = self.skewy_entry.get_value()
+        self.scale_button = QtWidgets.QPushButton(_("Apply Scale Factors"))
+        self.scale_button.setToolTip(
+            _("Apply Scale factors on the calibration points.")
+        )
+        self.scale_button.setStyleSheet("""
+                                      QPushButton
+                                      {
+                                          font-weight: bold;
+                                      }
+                                      """)
+        grid_lay.addWidget(self.scale_button, 31, 0, 1, 3)
 
-        # create a new object adjusted (calibrated)
-        def initialize_geometry(obj_init, app):
-            obj_init.solid_geometry = deepcopy(obj.solid_geometry)
-            try:
-                obj_init.follow_geometry = deepcopy(obj.follow_geometry)
-            except AttributeError:
-                pass
+        self.skewx_label = QtWidgets.QLabel(_("Skew Angle X:"))
+        self.skewx_label.setToolTip(
+            _("Angle for Skew action, in degrees.\n"
+              "Float number between -360 and 359.")
+        )
+        self.skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.skewx_entry.set_range(-360, 360)
+        self.skewx_entry.set_precision(self.decimals)
+        self.skewx_entry.setSingleStep(0.1)
 
-            try:
-                obj_init.apertures = deepcopy(obj.apertures)
-            except AttributeError:
-                pass
+        grid_lay.addWidget(self.skewx_label, 32, 0)
+        grid_lay.addWidget(self.skewx_entry, 32, 1, 1, 2)
 
-            try:
-                if obj.tools:
-                    obj_init.tools = deepcopy(obj.tools)
-            except Exception as ee:
-                log.debug("ToolCalibration.new_calibrated_object.initialize_geometry() --> %s" % str(ee))
+        self.skewy_label = QtWidgets.QLabel(_("Skew Angle Y:"))
+        self.skewy_label.setToolTip(
+            _("Angle for Skew action, in degrees.\n"
+              "Float number between -360 and 359.")
+        )
+        self.skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.skewy_entry.set_range(-360, 360)
+        self.skewy_entry.set_precision(self.decimals)
+        self.skewy_entry.setSingleStep(0.1)
 
-            obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
-            obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
+        grid_lay.addWidget(self.skewy_label, 33, 0)
+        grid_lay.addWidget(self.skewy_entry, 33, 1, 1, 2)
 
-            try:
-                obj_init.source_file = deepcopy(obj.source_file)
-            except (AttributeError, TypeError):
-                pass
+        self.skew_button = QtWidgets.QPushButton(_("Apply Skew Factors"))
+        self.skew_button.setToolTip(
+            _("Apply Skew factors on the calibration points.")
+        )
+        self.skew_button.setStyleSheet("""
+                                      QPushButton
+                                      {
+                                          font-weight: bold;
+                                      }
+                                      """)
+        grid_lay.addWidget(self.skew_button, 34, 0, 1, 3)
 
-        def initialize_gerber(obj_init, app):
-            obj_init.solid_geometry = deepcopy(obj.solid_geometry)
-            try:
-                obj_init.follow_geometry = deepcopy(obj.follow_geometry)
-            except AttributeError:
-                pass
+        # final_factors_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Final Factors"))
+        # final_factors_lbl.setToolTip(
+        #     _("Generate verification GCode file adjusted with\n"
+        #       "the factors above.")
+        # )
+        # grid_lay.addWidget(final_factors_lbl, 27, 0, 1, 3)
+        #
+        # self.fin_scalex_label = QtWidgets.QLabel(_("Scale Factor X:"))
+        # self.fin_scalex_label.setToolTip(
+        #     _("Final factor for Scale action over X axis.")
+        # )
+        # self.fin_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        # self.fin_scalex_entry.set_range(0, 9999.9999)
+        # self.fin_scalex_entry.set_precision(self.decimals)
+        # self.fin_scalex_entry.setSingleStep(0.1)
+        #
+        # grid_lay.addWidget(self.fin_scalex_label, 28, 0)
+        # grid_lay.addWidget(self.fin_scalex_entry, 28, 1, 1, 2)
+        #
+        # self.fin_scaley_label = QtWidgets.QLabel(_("Scale Factor Y:"))
+        # self.fin_scaley_label.setToolTip(
+        #     _("Final factor for Scale action over Y axis.")
+        # )
+        # self.fin_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        # self.fin_scaley_entry.set_range(0, 9999.9999)
+        # self.fin_scaley_entry.set_precision(self.decimals)
+        # self.fin_scaley_entry.setSingleStep(0.1)
+        #
+        # grid_lay.addWidget(self.fin_scaley_label, 29, 0)
+        # grid_lay.addWidget(self.fin_scaley_entry, 29, 1, 1, 2)
+        #
+        # self.fin_skewx_label = QtWidgets.QLabel(_("Skew Angle X:"))
+        # self.fin_skewx_label.setToolTip(
+        #     _("Final value for angle for Skew action, in degrees.\n"
+        #       "Float number between -360 and 359.")
+        # )
+        # self.fin_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        # self.fin_skewx_entry.set_range(-360, 360)
+        # self.fin_skewx_entry.set_precision(self.decimals)
+        # self.fin_skewx_entry.setSingleStep(0.1)
+        #
+        # grid_lay.addWidget(self.fin_skewx_label, 30, 0)
+        # grid_lay.addWidget(self.fin_skewx_entry, 30, 1, 1, 2)
+        #
+        # self.fin_skewy_label = QtWidgets.QLabel(_("Skew Angle Y:"))
+        # self.fin_skewy_label.setToolTip(
+        #     _("Final value for angle for Skew action, in degrees.\n"
+        #       "Float number between -360 and 359.")
+        # )
+        # self.fin_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        # self.fin_skewy_entry.set_range(-360, 360)
+        # self.fin_skewy_entry.set_precision(self.decimals)
+        # self.fin_skewy_entry.setSingleStep(0.1)
+        #
+        # grid_lay.addWidget(self.fin_skewy_label, 31, 0)
+        # grid_lay.addWidget(self.fin_skewy_entry, 31, 1, 1, 2)
 
-            try:
-                obj_init.apertures = deepcopy(obj.apertures)
-            except AttributeError:
-                pass
+        # ## Adjusted GCode Button
 
-            try:
-                if obj.tools:
-                    obj_init.tools = deepcopy(obj.tools)
-            except Exception as err:
-                log.debug("ToolCalibration.new_calibrated_object.initialize_gerber() --> %s" % str(err))
+        self.adj_gcode_button = QtWidgets.QPushButton(_("Generate Adjusted GCode"))
+        self.adj_gcode_button.setToolTip(
+            _("Generate verification GCode file adjusted with\n"
+              "the factors set above.\n"
+              "The GCode parameters can be readjusted\n"
+              "before clicking this button.")
+        )
+        self.adj_gcode_button.setStyleSheet("""
+                               QPushButton
+                               {
+                                   font-weight: bold;
+                               }
+                               """)
+        grid_lay.addWidget(self.adj_gcode_button, 42, 0, 1, 3)
 
-            obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
-            obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
+        separator_line1 = QtWidgets.QFrame()
+        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line1, 43, 0, 1, 3)
 
-            try:
-                obj_init.source_file = self.export_gerber(obj_name=obj_name, filename=None, local_use=obj_init,
-                                                          use_thread=False)
-            except (AttributeError, TypeError):
-                pass
+        grid_lay.addWidget(QtWidgets.QLabel(''), 44, 0, 1, 3)
 
-        def initialize_excellon(obj_init, app):
-            obj_init.tools = deepcopy(obj.tools)
+        # STEP 5 #
+        step_5 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 5: Calibrate FlatCAM Objects"))
+        step_5.setToolTip(
+            _("Adjust the FlatCAM objects\n"
+              "with the factors determined and verified above.")
+        )
+        grid_lay.addWidget(step_5, 45, 0, 1, 3)
 
-            # drills are offset, so they need to be deep copied
-            obj_init.drills = deepcopy(obj.drills)
-            # slots are offset, so they need to be deep copied
-            obj_init.slots = deepcopy(obj.slots)
+        self.adj_object_type_combo = FCComboBox()
+        self.adj_object_type_combo.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
 
-            obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
-            obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
+        self.adj_object_type_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
+        self.adj_object_type_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
+        self.adj_object_type_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
-            obj_init.create_geometry()
+        self.adj_object_type_label = QtWidgets.QLabel("%s:" % _("Adjusted object type"))
+        self.adj_object_type_label.setToolTip(_("Type of the FlatCAM Object to be adjusted."))
 
-            obj_init.source_file = self.app.export_excellon(obj_name=obj_name, local_use=obj, filename=None,
-                                                            use_thread=False)
+        grid_lay.addWidget(self.adj_object_type_label, 46, 0, 1, 3)
+        grid_lay.addWidget(self.adj_object_type_combo, 47, 0, 1, 3)
 
-        obj = self.cal_object
-        obj_name = obj_name
+        self.adj_object_combo = FCComboBox()
+        self.adj_object_combo.setModel(self.app.collection)
+        self.adj_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.adj_object_combo.is_last = True
+        self.adj_object_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.adj_object_type_combo.get_value()]
 
-        if obj is None:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no FlatCAM object selected..."))
-            log.debug("ToolCalibration.new_calibrated_object() --> No object to calibrate")
-            return 'fail'
+        self.adj_object_label = QtWidgets.QLabel("%s:" % _("Adjusted object selection"))
+        self.adj_object_label.setToolTip(
+            _("The FlatCAM Object to be adjusted.")
+        )
 
-        try:
-            if obj.kind.lower() == 'excellon':
-                self.app.app_obj.new_object("excellon", str(obj_name), initialize_excellon)
-            elif obj.kind.lower() == 'gerber':
-                self.app.app_obj.new_object("gerber", str(obj_name), initialize_gerber)
-            elif obj.kind.lower() == 'geometry':
-                self.app.app_obj.new_object("geometry", str(obj_name), initialize_geometry)
-        except Exception as e:
-            log.debug("ToolCalibration.new_calibrated_object() --> %s" % str(e))
-            return "Operation failed: %s" % str(e)
+        grid_lay.addWidget(self.adj_object_label, 48, 0, 1, 3)
+        grid_lay.addWidget(self.adj_object_combo, 49, 0, 1, 3)
 
-    def disconnect_cal_events(self):
-        # restore the Grid snapping if it was active before
-        if self.grid_status_memory is True:
-            self.app.ui.grid_snap_btn.trigger()
+        # ## Adjust Objects Button
+        self.cal_button = QtWidgets.QPushButton(_("Calibrate"))
+        self.cal_button.setToolTip(
+            _("Adjust (scale and/or skew) the objects\n"
+              "with the factors determined above.")
+        )
+        self.cal_button.setStyleSheet("""
+                               QPushButton
+                               {
+                                   font-weight: bold;
+                               }
+                               """)
+        grid_lay.addWidget(self.cal_button, 50, 0, 1, 3)
 
-        self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+        separator_line2 = QtWidgets.QFrame()
+        separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line2, 51, 0, 1, 3)
 
-        if self.app.is_legacy is False:
-            self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
-        else:
-            self.canvas.graph_event_disconnect(self.mr)
+        grid_lay.addWidget(QtWidgets.QLabel(''), 52, 0, 1, 3)
 
-        self.local_connected = False
+        self.layout.addStretch()
 
-    def reset_fields(self):
-        self.object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
-        self.adj_exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
-        self.adj_geo_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
+        # ## 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)
+        # #################################### 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)
 
-# end of file
+    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)

+ 1311 - 1283
appTools/ToolCopperThieving.py

@@ -36,8 +36,6 @@ log = logging.getLogger('base')
 class ToolCopperThieving(AppTool):
     work_finished = QtCore.pyqtSignal()
 
-    toolName = _("Copper Thieving Tool")
-
     def __init__(self, app):
         AppTool.__init__(self, app)
 
@@ -47,1534 +45,1564 @@ class ToolCopperThieving(AppTool):
         self.decimals = self.app.decimals
         self.units = self.app.defaults['units']
 
-        # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(title_label)
-        self.layout.addWidget(QtWidgets.QLabel(''))
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = ThievingUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
 
-        # ## Grid Layout
-        i_grid_lay = QtWidgets.QGridLayout()
-        self.layout.addLayout(i_grid_lay)
-        i_grid_lay.setColumnStretch(0, 0)
-        i_grid_lay.setColumnStretch(1, 1)
+        # Objects involved in Copper thieving
+        self.grb_object = None
+        self.ref_obj = None
+        self.sel_rect = []
+        self.sm_object = None
 
-        self.grb_object_combo = FCComboBox()
-        self.grb_object_combo.setModel(self.app.collection)
-        self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.grb_object_combo.is_last = True
-        self.grb_object_combo.obj_type = 'Gerber'
+        # store the flattened geometry here:
+        self.flat_geometry = []
 
-        self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
-        self.grbobj_label.setToolTip(
-            _("Gerber Object to which will be added a copper thieving.")
-        )
+        # Events ID
+        self.mr = None
+        self.mm = None
 
-        i_grid_lay.addWidget(self.grbobj_label, 0, 0)
-        i_grid_lay.addWidget(self.grb_object_combo, 1, 0, 1, 2)
+        # Mouse cursor positions
+        self.mouse_is_dragging = False
+        self.cursor_pos = (0, 0)
+        self.first_click = False
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        i_grid_lay.addWidget(separator_line, 2, 0, 1, 2)
+        self.area_method = False
 
-        # ## Grid Layout
-        grid_lay = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_lay)
-        grid_lay.setColumnStretch(0, 0)
-        grid_lay.setColumnStretch(1, 1)
+        # Tool properties
+        self.clearance_val = None
+        self.margin_val = None
+        self.geo_steps_per_circle = 128
 
-        self.copper_fill_label = QtWidgets.QLabel('<b>%s</b>' % _('Parameters'))
-        self.copper_fill_label.setToolTip(
-            _("Parameters used for this tool.")
-        )
-        grid_lay.addWidget(self.copper_fill_label, 0, 0, 1, 2)
+        # Thieving geometry storage
+        self.new_solid_geometry = []
 
-        # CLEARANCE #
-        self.clearance_label = QtWidgets.QLabel('%s:' % _("Clearance"))
-        self.clearance_label.setToolTip(
-            _("This set the distance between the copper thieving components\n"
-              "(the polygon fill may be split in multiple polygons)\n"
-              "and the copper traces in the Gerber file.")
-        )
-        self.clearance_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.clearance_entry.set_range(0.00001, 9999.9999)
-        self.clearance_entry.set_precision(self.decimals)
-        self.clearance_entry.setSingleStep(0.1)
+        # Robber bar geometry storage
+        self.robber_geo = None
+        self.robber_line = None
 
-        grid_lay.addWidget(self.clearance_label, 1, 0)
-        grid_lay.addWidget(self.clearance_entry, 1, 1)
+        self.rb_thickness = None
 
-        # MARGIN #
-        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
-        self.margin_label.setToolTip(
-            _("Bounding box margin.")
-        )
-        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.margin_entry.set_range(0.0, 9999.9999)
-        self.margin_entry.set_precision(self.decimals)
-        self.margin_entry.setSingleStep(0.1)
+        # SIGNALS
+        self.ui.ref_combo_type.currentIndexChanged.connect(self.on_ref_combo_type_change)
+        self.ui.reference_radio.group_toggle_fn = self.on_toggle_reference
+        self.ui.fill_type_radio.activated_custom.connect(self.on_thieving_type)
 
-        grid_lay.addWidget(self.margin_label, 2, 0)
-        grid_lay.addWidget(self.margin_entry, 2, 1)
+        self.ui.fill_button.clicked.connect(self.execute)
+        self.ui.rb_button.clicked.connect(self.add_robber_bar)
+        self.ui.ppm_button.clicked.connect(self.on_add_ppm)
+        self.ui.reset_button.clicked.connect(self.set_tool_ui)
 
-        # Reference #
-        self.reference_radio = RadioSet([
-            {'label': _('Itself'), 'value': 'itself'},
-            {"label": _("Area Selection"), "value": "area"},
-            {'label':  _("Reference Object"), 'value': 'box'}
-        ], orientation='vertical', stretch=False)
-        self.reference_label = QtWidgets.QLabel(_("Reference:"))
-        self.reference_label.setToolTip(
-            _("- 'Itself' - the copper thieving extent is based on the object extent.\n"
-              "- 'Area Selection' - left mouse click to start selection of the area to be filled.\n"
-              "- 'Reference Object' - will do copper thieving within the area specified by another object.")
-        )
-        grid_lay.addWidget(self.reference_label, 3, 0)
-        grid_lay.addWidget(self.reference_radio, 3, 1)
+        self.work_finished.connect(self.on_new_pattern_plating_object)
 
-        self.ref_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
-        self.ref_combo_type_label.setToolTip(
-            _("The type of FlatCAM object to be used as copper thieving reference.\n"
-              "It can be Gerber, Excellon or Geometry.")
-        )
-        self.ref_combo_type = FCComboBox()
-        self.ref_combo_type.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
+    def run(self, toggle=True):
+        self.app.defaults.report_usage("ToolCopperThieving()")
 
-        grid_lay.addWidget(self.ref_combo_type_label, 4, 0)
-        grid_lay.addWidget(self.ref_combo_type, 4, 1)
+        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])
 
-        self.ref_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
-        self.ref_combo_label.setToolTip(
-            _("The FlatCAM object to be used as non copper clearing reference.")
-        )
-        self.ref_combo = FCComboBox()
-        self.ref_combo.setModel(self.app.collection)
-        self.ref_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.ref_combo.is_last = True
-        self.ref_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
-        }[self.ref_combo_type.get_value()]
+        AppTool.run(self)
 
-        grid_lay.addWidget(self.ref_combo_label, 5, 0)
-        grid_lay.addWidget(self.ref_combo, 5, 1)
+        self.set_tool_ui()
 
-        self.ref_combo.hide()
-        self.ref_combo_label.hide()
-        self.ref_combo_type.hide()
-        self.ref_combo_type_label.hide()
+        self.app.ui.notebook.setTabText(2, _("Copper Thieving Tool"))
 
-        # Bounding Box Type #
-        self.bbox_type_radio = RadioSet([
-            {'label': _('Rectangular'), 'value': 'rect'},
-            {"label": _("Minimal"), "value": "min"}
-        ], stretch=False)
-        self.bbox_type_label = QtWidgets.QLabel(_("Box Type:"))
-        self.bbox_type_label.setToolTip(
-            _("- 'Rectangular' - the bounding box will be of rectangular shape.\n"
-              "- 'Minimal' - the bounding box will be the convex hull shape.")
-        )
-        grid_lay.addWidget(self.bbox_type_label, 6, 0)
-        grid_lay.addWidget(self.bbox_type_radio, 6, 1)
-        self.bbox_type_label.hide()
-        self.bbox_type_radio.hide()
+    def install(self, icon=None, separator=None, **kwargs):
+        AppTool.install(self, icon, separator, shortcut='Alt+J', **kwargs)
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line, 7, 0, 1, 2)
+    def set_tool_ui(self):
+        self.units = self.app.defaults['units']
+        self.geo_steps_per_circle = int(self.app.defaults["tools_copper_thieving_circle_steps"])
 
-        # Fill Type
-        self.fill_type_radio = RadioSet([
-            {'label': _('Solid'), 'value': 'solid'},
-            {"label": _("Dots Grid"), "value": "dot"},
-            {"label": _("Squares Grid"), "value": "square"},
-            {"label": _("Lines Grid"), "value": "line"}
-        ], orientation='vertical', stretch=False)
-        self.fill_type_label = QtWidgets.QLabel(_("Fill Type:"))
-        self.fill_type_label.setToolTip(
-            _("- 'Solid' - copper thieving will be a solid polygon.\n"
-              "- 'Dots Grid' - the empty area will be filled with a pattern of dots.\n"
-              "- 'Squares Grid' - the empty area will be filled with a pattern of squares.\n"
-              "- 'Lines Grid' - the empty area will be filled with a pattern of lines.")
-        )
-        grid_lay.addWidget(self.fill_type_label, 8, 0)
-        grid_lay.addWidget(self.fill_type_radio, 8, 1)
+        self.ui.clearance_entry.set_value(float(self.app.defaults["tools_copper_thieving_clearance"]))
+        self.ui.margin_entry.set_value(float(self.app.defaults["tools_copper_thieving_margin"]))
+        self.ui.reference_radio.set_value(self.app.defaults["tools_copper_thieving_reference"])
+        self.ui.bbox_type_radio.set_value(self.app.defaults["tools_copper_thieving_box_type"])
+        self.ui.fill_type_radio.set_value(self.app.defaults["tools_copper_thieving_fill_type"])
 
-        # DOTS FRAME
-        self.dots_frame = QtWidgets.QFrame()
-        self.dots_frame.setContentsMargins(0, 0, 0, 0)
-        self.layout.addWidget(self.dots_frame)
-        dots_grid = QtWidgets.QGridLayout()
-        dots_grid.setColumnStretch(0, 0)
-        dots_grid.setColumnStretch(1, 1)
-        dots_grid.setContentsMargins(0, 0, 0, 0)
-        self.dots_frame.setLayout(dots_grid)
-        self.dots_frame.hide()
+        self.ui.dot_dia_entry.set_value(self.app.defaults["tools_copper_thieving_dots_dia"])
+        self.ui.dot_spacing_entry.set_value(self.app.defaults["tools_copper_thieving_dots_spacing"])
+        self.ui.square_size_entry.set_value(self.app.defaults["tools_copper_thieving_squares_size"])
+        self.ui.squares_spacing_entry.set_value(self.app.defaults["tools_copper_thieving_squares_spacing"])
+        self.ui.line_size_entry.set_value(self.app.defaults["tools_copper_thieving_lines_size"])
+        self.ui.lines_spacing_entry.set_value(self.app.defaults["tools_copper_thieving_lines_spacing"])
 
-        self.dots_label = QtWidgets.QLabel('<b>%s</b>:' % _("Dots Grid Parameters"))
-        dots_grid.addWidget(self.dots_label, 0, 0, 1, 2)
+        self.ui.rb_margin_entry.set_value(self.app.defaults["tools_copper_thieving_rb_margin"])
+        self.ui.rb_thickness_entry.set_value(self.app.defaults["tools_copper_thieving_rb_thickness"])
+        self.ui.clearance_ppm_entry.set_value(self.app.defaults["tools_copper_thieving_mask_clearance"])
 
-        # Dot diameter #
-        self.dotdia_label = QtWidgets.QLabel('%s:' % _("Dia"))
-        self.dotdia_label.setToolTip(
-            _("Dot diameter in Dots Grid.")
-        )
-        self.dot_dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.dot_dia_entry.set_range(0.0, 9999.9999)
-        self.dot_dia_entry.set_precision(self.decimals)
-        self.dot_dia_entry.setSingleStep(0.1)
+        # INIT SECTION
+        self.area_method = False
+        self.robber_geo = None
+        self.robber_line = None
+        self.new_solid_geometry = None
 
-        dots_grid.addWidget(self.dotdia_label, 1, 0)
-        dots_grid.addWidget(self.dot_dia_entry, 1, 1)
+    def on_ref_combo_type_change(self):
+        obj_type = self.ui.ref_combo_type.currentIndex()
+        self.ui.ref_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.ui.ref_combo.setCurrentIndex(0)
+        self.ui.ref_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.ui.ref_combo_type.get_value()]
 
-        # Dot spacing #
-        self.dotspacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
-        self.dotspacing_label.setToolTip(
-            _("Distance between each two dots in Dots Grid.")
-        )
-        self.dot_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.dot_spacing_entry.set_range(0.0, 9999.9999)
-        self.dot_spacing_entry.set_precision(self.decimals)
-        self.dot_spacing_entry.setSingleStep(0.1)
+    def on_toggle_reference(self):
+        if self.ui.reference_radio.get_value() == "itself" or self.ui.reference_radio.get_value() == "area":
+            self.ui.ref_combo.hide()
+            self.ui.ref_combo_label.hide()
+            self.ui.ref_combo_type.hide()
+            self.ui.ref_combo_type_label.hide()
+        else:
+            self.ui.ref_combo.show()
+            self.ui.ref_combo_label.show()
+            self.ui.ref_combo_type.show()
+            self.ui.ref_combo_type_label.show()
+
+        if self.ui.reference_radio.get_value() == "itself":
+            self.ui.bbox_type_label.show()
+            self.ui.bbox_type_radio.show()
+        else:
+            if self.ui.fill_type_radio.get_value() == 'line':
+                self.ui.reference_radio.set_value('itself')
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Lines Grid works only for 'itself' reference ..."))
+                return
 
-        dots_grid.addWidget(self.dotspacing_label, 2, 0)
-        dots_grid.addWidget(self.dot_spacing_entry, 2, 1)
+            self.ui.bbox_type_label.hide()
+            self.ui.bbox_type_radio.hide()
 
-        # SQUARES FRAME
-        self.squares_frame = QtWidgets.QFrame()
-        self.squares_frame.setContentsMargins(0, 0, 0, 0)
-        self.layout.addWidget(self.squares_frame)
-        squares_grid = QtWidgets.QGridLayout()
-        squares_grid.setColumnStretch(0, 0)
-        squares_grid.setColumnStretch(1, 1)
-        squares_grid.setContentsMargins(0, 0, 0, 0)
-        self.squares_frame.setLayout(squares_grid)
-        self.squares_frame.hide()
+    def on_thieving_type(self, choice):
+        if choice == 'solid':
+            self.ui.dots_frame.hide()
+            self.ui.squares_frame.hide()
+            self.ui.lines_frame.hide()
+            self.app.inform.emit(_("Solid fill selected."))
+        elif choice == 'dot':
+            self.ui.dots_frame.show()
+            self.ui.squares_frame.hide()
+            self.ui.lines_frame.hide()
+            self.app.inform.emit(_("Dots grid fill selected."))
+        elif choice == 'square':
+            self.ui.dots_frame.hide()
+            self.ui.squares_frame.show()
+            self.ui.lines_frame.hide()
+            self.app.inform.emit(_("Squares grid fill selected."))
+        else:
+            if self.ui.reference_radio.get_value() != 'itself':
+                self.ui.reference_radio.set_value('itself')
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Lines Grid works only for 'itself' reference ..."))
+            self.ui.dots_frame.hide()
+            self.ui.squares_frame.hide()
+            self.ui.lines_frame.show()
 
-        self.squares_label = QtWidgets.QLabel('<b>%s</b>:' % _("Squares Grid Parameters"))
-        squares_grid.addWidget(self.squares_label, 0, 0, 1, 2)
+    def add_robber_bar(self):
+        rb_margin = self.ui.rb_margin_entry.get_value()
+        self.rb_thickness = self.ui.rb_thickness_entry.get_value()
 
-        # Square Size #
-        self.square_size_label = QtWidgets.QLabel('%s:' % _("Size"))
-        self.square_size_label.setToolTip(
-            _("Square side size in Squares Grid.")
-        )
-        self.square_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.square_size_entry.set_range(0.0, 9999.9999)
-        self.square_size_entry.set_precision(self.decimals)
-        self.square_size_entry.setSingleStep(0.1)
+        # get the Gerber object on which the Robber bar will be inserted
+        selection_index = self.ui.grb_object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.ui.grb_object_combo.rootModelIndex())
 
-        squares_grid.addWidget(self.square_size_label, 1, 0)
-        squares_grid.addWidget(self.square_size_entry, 1, 1)
+        try:
+            self.grb_object = model_index.internalPointer().obj
+        except Exception as e:
+            log.debug("ToolCopperThieving.add_robber_bar() --> %s" % str(e))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
+            return
 
-        # Squares spacing #
-        self.squares_spacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
-        self.squares_spacing_label.setToolTip(
-            _("Distance between each two squares in Squares Grid.")
-        )
-        self.squares_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.squares_spacing_entry.set_range(0.0, 9999.9999)
-        self.squares_spacing_entry.set_precision(self.decimals)
-        self.squares_spacing_entry.setSingleStep(0.1)
+        try:
+            outline_pol = self.grb_object.solid_geometry.envelope
+        except TypeError:
+            outline_pol = MultiPolygon(self.grb_object.solid_geometry).envelope
 
-        squares_grid.addWidget(self.squares_spacing_label, 2, 0)
-        squares_grid.addWidget(self.squares_spacing_entry, 2, 1)
+        rb_distance = rb_margin + (self.rb_thickness / 2.0)
+        self.robber_line = outline_pol.buffer(rb_distance).exterior
 
-        # LINES FRAME
-        self.lines_frame = QtWidgets.QFrame()
-        self.lines_frame.setContentsMargins(0, 0, 0, 0)
-        self.layout.addWidget(self.lines_frame)
-        lines_grid = QtWidgets.QGridLayout()
-        lines_grid.setColumnStretch(0, 0)
-        lines_grid.setColumnStretch(1, 1)
-        lines_grid.setContentsMargins(0, 0, 0, 0)
-        self.lines_frame.setLayout(lines_grid)
-        self.lines_frame.hide()
+        self.robber_geo = self.robber_line.buffer(self.rb_thickness / 2.0)
 
-        self.lines_label = QtWidgets.QLabel('<b>%s</b>:' % _("Lines Grid Parameters"))
-        lines_grid.addWidget(self.lines_label, 0, 0, 1, 2)
+        self.app.proc_container.update_view_text(' %s' % _("Append geometry"))
 
-        # Square Size #
-        self.line_size_label = QtWidgets.QLabel('%s:' % _("Size"))
-        self.line_size_label.setToolTip(
-            _("Line thickness size in Lines Grid.")
-        )
-        self.line_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.line_size_entry.set_range(0.0, 9999.9999)
-        self.line_size_entry.set_precision(self.decimals)
-        self.line_size_entry.setSingleStep(0.1)
+        aperture_found = None
+        for ap_id, ap_val in self.grb_object.apertures.items():
+            if ap_val['type'] == 'C' and ap_val['size'] == self.rb_thickness:
+                aperture_found = ap_id
+                break
 
-        lines_grid.addWidget(self.line_size_label, 1, 0)
-        lines_grid.addWidget(self.line_size_entry, 1, 1)
+        if aperture_found:
+            geo_elem = {}
+            geo_elem['solid'] = self.robber_geo
+            geo_elem['follow'] = self.robber_line
+            self.grb_object.apertures[aperture_found]['geometry'].append(deepcopy(geo_elem))
+        else:
+            ap_keys = list(self.grb_object.apertures.keys())
+            if ap_keys:
+                new_apid = str(int(max(ap_keys)) + 1)
+            else:
+                new_apid = '10'
 
-        # Lines spacing #
-        self.lines_spacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
-        self.lines_spacing_label.setToolTip(
-            _("Distance between each two lines in Lines Grid.")
-        )
-        self.lines_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.lines_spacing_entry.set_range(0.0, 9999.9999)
-        self.lines_spacing_entry.set_precision(self.decimals)
-        self.lines_spacing_entry.setSingleStep(0.1)
+            self.grb_object.apertures[new_apid] = {}
+            self.grb_object.apertures[new_apid]['type'] = 'C'
+            self.grb_object.apertures[new_apid]['size'] = self.rb_thickness
+            self.grb_object.apertures[new_apid]['geometry'] = []
 
-        lines_grid.addWidget(self.lines_spacing_label, 2, 0)
-        lines_grid.addWidget(self.lines_spacing_entry, 2, 1)
+            geo_elem = {}
+            geo_elem['solid'] = self.robber_geo
+            geo_elem['follow'] = self.robber_line
+            self.grb_object.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
 
-        # ## Insert Copper Thieving
-        self.fill_button = QtWidgets.QPushButton(_("Insert Copper thieving"))
-        self.fill_button.setToolTip(
-            _("Will add a polygon (may be split in multiple parts)\n"
-              "that will surround the actual Gerber traces at a certain distance.")
-        )
-        self.fill_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(self.fill_button)
+        geo_obj = self.grb_object.solid_geometry
+        if isinstance(geo_obj, MultiPolygon):
+            s_list = []
+            for pol in geo_obj.geoms:
+                s_list.append(pol)
+            s_list.append(self.robber_geo)
+            geo_obj = MultiPolygon(s_list)
+        elif isinstance(geo_obj, list):
+            geo_obj.append(self.robber_geo)
+        elif isinstance(geo_obj, Polygon):
+            geo_obj = MultiPolygon([geo_obj, self.robber_geo])
 
-        # ## Grid Layout
-        grid_lay_1 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_lay_1)
-        grid_lay_1.setColumnStretch(0, 0)
-        grid_lay_1.setColumnStretch(1, 1)
-        grid_lay_1.setColumnStretch(2, 0)
+        self.grb_object.solid_geometry = geo_obj
 
-        separator_line_1 = QtWidgets.QFrame()
-        separator_line_1.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line_1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay_1.addWidget(separator_line_1, 0, 0, 1, 3)
+        self.app.proc_container.update_view_text(' %s' % _("Append source file"))
+        # update the source file with the new geometry:
+        self.grb_object.source_file = self.app.export_gerber(obj_name=self.grb_object.options['name'],
+                                                             filename=None,
+                                                             local_use=self.grb_object,
+                                                             use_thread=False)
+        self.app.proc_container.update_view_text(' %s' % '')
+        self.on_exit()
+        self.app.inform.emit('[success] %s' % _("Copper Thieving Tool done."))
 
-        grid_lay_1.addWidget(QtWidgets.QLabel(''))
+    def execute(self):
+        self.app.call_source = "copper_thieving_tool"
 
-        self.robber_bar_label = QtWidgets.QLabel('<b>%s</b>' % _('Robber Bar Parameters'))
-        self.robber_bar_label.setToolTip(
-            _("Parameters used for the robber bar.\n"
-              "Robber bar = copper border to help in pattern hole plating.")
-        )
-        grid_lay_1.addWidget(self.robber_bar_label, 1, 0, 1, 3)
+        self.clearance_val = self.ui.clearance_entry.get_value()
+        self.margin_val = self.ui.margin_entry.get_value()
+        reference_method = self.ui.reference_radio.get_value()
 
-        # ROBBER BAR MARGIN #
-        self.rb_margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
-        self.rb_margin_label.setToolTip(
-            _("Bounding box margin for robber bar.")
-        )
-        self.rb_margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.rb_margin_entry.set_range(-9999.9999, 9999.9999)
-        self.rb_margin_entry.set_precision(self.decimals)
-        self.rb_margin_entry.setSingleStep(0.1)
+        # get the Gerber object on which the Copper thieving will be inserted
+        selection_index = self.ui.grb_object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.ui.grb_object_combo.rootModelIndex())
 
-        grid_lay_1.addWidget(self.rb_margin_label, 2, 0)
-        grid_lay_1.addWidget(self.rb_margin_entry, 2, 1, 1, 2)
+        try:
+            self.grb_object = model_index.internalPointer().obj
+        except Exception as e:
+            log.debug("ToolCopperThieving.execute() --> %s" % str(e))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
+            return
 
-        # THICKNESS #
-        self.rb_thickness_label = QtWidgets.QLabel('%s:' % _("Thickness"))
-        self.rb_thickness_label.setToolTip(
-            _("The robber bar thickness.")
-        )
-        self.rb_thickness_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.rb_thickness_entry.set_range(0.0000, 9999.9999)
-        self.rb_thickness_entry.set_precision(self.decimals)
-        self.rb_thickness_entry.setSingleStep(0.1)
+        if reference_method == 'itself':
+            bound_obj_name = self.ui.grb_object_combo.currentText()
 
-        grid_lay_1.addWidget(self.rb_thickness_label, 3, 0)
-        grid_lay_1.addWidget(self.rb_thickness_entry, 3, 1, 1, 2)
+            # Get reference object.
+            try:
+                self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
+            except Exception as e:
+                self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(e)))
+                return "Could not retrieve object: %s" % self.obj_name
 
-        # ## Insert Robber Bar
-        self.rb_button = QtWidgets.QPushButton(_("Insert Robber Bar"))
-        self.rb_button.setToolTip(
-            _("Will add a polygon with a defined thickness\n"
-              "that will surround the actual Gerber object\n"
-              "at a certain distance.\n"
-              "Required when doing holes pattern plating.")
-        )
-        self.rb_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay_1.addWidget(self.rb_button, 4, 0, 1, 3)
+            self.on_copper_thieving(
+                thieving_obj=self.grb_object,
+                c_val=self.clearance_val,
+                margin=self.margin_val
+            )
 
-        separator_line_2 = QtWidgets.QFrame()
-        separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay_1.addWidget(separator_line_2, 5, 0, 1, 3)
+        elif reference_method == 'area':
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
 
-        self.patern_mask_label = QtWidgets.QLabel('<b>%s</b>' % _('Pattern Plating Mask'))
-        self.patern_mask_label.setToolTip(
-            _("Generate a mask for pattern plating.")
-        )
-        grid_lay_1.addWidget(self.patern_mask_label, 6, 0, 1, 3)
+            self.area_method = True
 
-        self.sm_obj_label = QtWidgets.QLabel("%s:" % _("Select Soldermask object"))
-        self.sm_obj_label.setToolTip(
-            _("Gerber Object with the soldermask.\n"
-              "It will be used as a base for\n"
-              "the pattern plating mask.")
-        )
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
 
-        self.sm_object_combo = FCComboBox()
-        self.sm_object_combo.setModel(self.app.collection)
-        self.sm_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.sm_object_combo.is_last = True
-        self.sm_object_combo.obj_type = 'Gerber'
+            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
+            self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
 
-        grid_lay_1.addWidget(self.sm_obj_label, 7, 0, 1, 3)
-        grid_lay_1.addWidget(self.sm_object_combo, 8, 0, 1, 3)
+        elif reference_method == 'box':
+            bound_obj_name = self.ui.ref_combo.currentText()
 
-        # Openings CLEARANCE #
-        self.clearance_ppm_label = QtWidgets.QLabel('%s:' % _("Clearance"))
-        self.clearance_ppm_label.setToolTip(
-            _("The distance between the possible copper thieving elements\n"
-              "and/or robber bar and the actual openings in the mask.")
-        )
-        self.clearance_ppm_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.clearance_ppm_entry.set_range(-9999.9999, 9999.9999)
-        self.clearance_ppm_entry.set_precision(self.decimals)
-        self.clearance_ppm_entry.setSingleStep(0.1)
+            # Get reference object.
+            try:
+                self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
+            except Exception:
+                self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), bound_obj_name))
+                return
 
-        grid_lay_1.addWidget(self.clearance_ppm_label, 9, 0)
-        grid_lay_1.addWidget(self.clearance_ppm_entry, 9, 1, 1, 2)
+            self.on_copper_thieving(
+                thieving_obj=self.grb_object,
+                ref_obj=self.ref_obj,
+                c_val=self.clearance_val,
+                margin=self.margin_val
+            )
 
-        # Plated area
-        self.plated_area_label = QtWidgets.QLabel('%s:' % _("Plated area"))
-        self.plated_area_label.setToolTip(
-            _("The area to be plated by pattern plating.\n"
-              "Basically is made from the openings in the plating mask.\n\n"
-              "<<WARNING>> - the calculated area is actually a bit larger\n"
-              "due of the fact that the soldermask openings are by design\n"
-              "a bit larger than the copper pads, and this area is\n"
-              "calculated from the soldermask openings.")
-        )
-        self.plated_area_entry = FCEntry()
-        self.plated_area_entry.setDisabled(True)
+        # To be called after clicking on the plot.
 
-        if self.units.upper() == 'MM':
-            self.units_area_label = QtWidgets.QLabel('%s<sup>2</sup>' % _("mm"))
+    def on_mouse_release(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            # event_is_dragging = event.is_dragging
+            right_button = 2
         else:
-            self.units_area_label = QtWidgets.QLabel('%s<sup>2</sup>' % _("in"))
+            event_pos = (event.xdata, event.ydata)
+            # event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
 
-        grid_lay_1.addWidget(self.plated_area_label, 10, 0)
-        grid_lay_1.addWidget(self.plated_area_entry, 10, 1)
-        grid_lay_1.addWidget(self.units_area_label, 10, 2)
+        event_pos = self.app.plotcanvas.translate_coords(event_pos)
 
-        # ## Pattern Plating Mask
-        self.ppm_button = QtWidgets.QPushButton(_("Generate pattern plating mask"))
-        self.ppm_button.setToolTip(
-            _("Will add to the soldermask gerber geometry\n"
-              "the geometries of the copper thieving and/or\n"
-              "the robber bar if those were generated.")
-        )
-        self.ppm_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay_1.addWidget(self.ppm_button, 11, 0, 1, 3)
+        # do clear area only for left mouse clicks
+        if event.button == 1:
+            if self.first_click is False:
+                self.first_click = True
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the filling area."))
 
-        self.layout.addStretch()
+                self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
+                if self.app.grid_status() is True:
+                    self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
+            else:
+                self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
+                self.app.delete_selection_shape()
 
-        # ## 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)
+                if self.app.grid_status() is True:
+                    curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
+                else:
+                    curr_pos = (event_pos[0], event_pos[1])
 
-        # Objects involved in Copper thieving
-        self.grb_object = None
-        self.ref_obj = None
-        self.sel_rect = []
-        self.sm_object = None
+                x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
+                x1, y1 = curr_pos[0], curr_pos[1]
+                pt1 = (x0, y0)
+                pt2 = (x1, y0)
+                pt3 = (x1, y1)
+                pt4 = (x0, y1)
 
-        # store the flattened geometry here:
-        self.flat_geometry = []
+                new_rectangle = Polygon([pt1, pt2, pt3, pt4])
+                self.sel_rect.append(new_rectangle)
 
-        # Events ID
-        self.mr = None
-        self.mm = None
+                # add a temporary shape on canvas
+                self.draw_tool_selection_shape(old_coords=(x0, y0), coords=(x1, y1))
+                self.first_click = False
+                return
 
-        # Mouse cursor positions
-        self.mouse_is_dragging = False
-        self.cursor_pos = (0, 0)
-        self.first_click = False
+        elif event.button == right_button and self.mouse_is_dragging is False:
+            self.area_method = False
+            self.first_click = False
 
-        self.area_method = False
+            self.delete_tool_selection_shape()
 
-        # Tool properties
-        self.clearance_val = None
-        self.margin_val = None
-        self.geo_steps_per_circle = 128
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
 
-        # Thieving geometry storage
-        self.new_solid_geometry = []
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+                                                                  self.app.on_mouse_click_over_plot)
+            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
+                                                                  self.app.on_mouse_move_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
 
-        # Robber bar geometry storage
-        self.robber_geo = None
-        self.robber_line = None
+            if len(self.sel_rect) == 0:
+                return
 
-        self.rb_thickness = None
+            self.sel_rect = cascaded_union(self.sel_rect)
 
-        # SIGNALS
-        self.ref_combo_type.currentIndexChanged.connect(self.on_ref_combo_type_change)
-        self.reference_radio.group_toggle_fn = self.on_toggle_reference
-        self.fill_type_radio.activated_custom.connect(self.on_thieving_type)
+            if not isinstance(self.sel_rect, Iterable):
+                self.sel_rect = [self.sel_rect]
 
-        self.fill_button.clicked.connect(self.execute)
-        self.rb_button.clicked.connect(self.add_robber_bar)
-        self.ppm_button.clicked.connect(self.on_add_ppm)
-        self.reset_button.clicked.connect(self.set_tool_ui)
+            self.on_copper_thieving(
+                thieving_obj=self.grb_object,
+                ref_obj=self.sel_rect,
+                c_val=self.clearance_val,
+                margin=self.margin_val
+            )
 
-        self.work_finished.connect(self.on_new_pattern_plating_object)
+    # called on mouse move
+    def on_mouse_move(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            # right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            # right_button = 3
 
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolCopperThieving()")
+        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
 
-        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
+        # detect mouse dragging motion
+        if event_is_dragging is True:
+            self.mouse_is_dragging = True
         else:
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
+            self.mouse_is_dragging = False
 
-        AppTool.run(self)
+        # update the cursor position
+        if self.app.grid_status() is True:
+            # Update cursor
+            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
 
-        self.set_tool_ui()
+            self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
+                                         symbol='++', edge_color=self.app.cursor_color_3D,
+                                         edge_width=self.app.defaults["global_cursor_width"],
+                                         size=self.app.defaults["global_cursor_size"])
 
-        self.app.ui.notebook.setTabText(2, _("Copper Thieving Tool"))
+        if self.cursor_pos is None:
+            self.cursor_pos = (0, 0)
 
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Alt+J', **kwargs)
+        self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
+        self.app.dy = curr_pos[1] - float(self.cursor_pos[1])
 
-    def set_tool_ui(self):
-        self.units = self.app.defaults['units']
-        self.clearance_entry.set_value(float(self.app.defaults["tools_copper_thieving_clearance"]))
-        self.margin_entry.set_value(float(self.app.defaults["tools_copper_thieving_margin"]))
-        self.reference_radio.set_value(self.app.defaults["tools_copper_thieving_reference"])
-        self.bbox_type_radio.set_value(self.app.defaults["tools_copper_thieving_box_type"])
-        self.fill_type_radio.set_value(self.app.defaults["tools_copper_thieving_fill_type"])
-        self.geo_steps_per_circle = int(self.app.defaults["tools_copper_thieving_circle_steps"])
+        # # update the positions on status bar
+        self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f&nbsp;" % (curr_pos[0], curr_pos[1]))
+        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
 
-        self.dot_dia_entry.set_value(self.app.defaults["tools_copper_thieving_dots_dia"])
-        self.dot_spacing_entry.set_value(self.app.defaults["tools_copper_thieving_dots_spacing"])
-        self.square_size_entry.set_value(self.app.defaults["tools_copper_thieving_squares_size"])
-        self.squares_spacing_entry.set_value(self.app.defaults["tools_copper_thieving_squares_spacing"])
-        self.line_size_entry.set_value(self.app.defaults["tools_copper_thieving_lines_size"])
-        self.lines_spacing_entry.set_value(self.app.defaults["tools_copper_thieving_lines_spacing"])
+        units = self.app.defaults["units"].lower()
+        self.app.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
 
-        self.rb_margin_entry.set_value(self.app.defaults["tools_copper_thieving_rb_margin"])
-        self.rb_thickness_entry.set_value(self.app.defaults["tools_copper_thieving_rb_thickness"])
-        self.clearance_ppm_entry.set_value(self.app.defaults["tools_copper_thieving_mask_clearance"])
+        # draw the utility geometry
+        if self.first_click:
+            self.app.delete_selection_shape()
+            self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
+                                                 coords=(curr_pos[0], curr_pos[1]))
 
-        # INIT SECTION
-        self.area_method = False
-        self.robber_geo = None
-        self.robber_line = None
-        self.new_solid_geometry = None
+    def on_copper_thieving(self, thieving_obj, ref_obj=None, c_val=None, margin=None, run_threaded=True):
+        """
 
-    def on_ref_combo_type_change(self):
-        obj_type = self.ref_combo_type.currentIndex()
-        self.ref_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.ref_combo.setCurrentIndex(0)
-        self.ref_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
-        }[self.ref_combo_type.get_value()]
+        :param thieving_obj:
+        :param ref_obj:
+        :param c_val:
+        :param margin:
+        :param run_threaded:
+        :return:
+        """
 
-    def on_toggle_reference(self):
-        if self.reference_radio.get_value() == "itself" or self.reference_radio.get_value() == "area":
-            self.ref_combo.hide()
-            self.ref_combo_label.hide()
-            self.ref_combo_type.hide()
-            self.ref_combo_type_label.hide()
-        else:
-            self.ref_combo.show()
-            self.ref_combo_label.show()
-            self.ref_combo_type.show()
-            self.ref_combo_type_label.show()
-
-        if self.reference_radio.get_value() == "itself":
-            self.bbox_type_label.show()
-            self.bbox_type_radio.show()
+        if run_threaded:
+            self.app.proc_container.new('%s ...' % _("Thieving"))
         else:
-            if self.fill_type_radio.get_value() == 'line':
-                self.reference_radio.set_value('itself')
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Lines Grid works only for 'itself' reference ..."))
-                return
+            QtWidgets.QApplication.processEvents()
 
-            self.bbox_type_label.hide()
-            self.bbox_type_radio.hide()
+        self.app.proc_container.view.set_busy('%s ...' % _("Thieving"))
 
-    def on_thieving_type(self, choice):
-        if choice == 'solid':
-            self.dots_frame.hide()
-            self.squares_frame.hide()
-            self.lines_frame.hide()
-            self.app.inform.emit(_("Solid fill selected."))
-        elif choice == 'dot':
-            self.dots_frame.show()
-            self.squares_frame.hide()
-            self.lines_frame.hide()
-            self.app.inform.emit(_("Dots grid fill selected."))
-        elif choice == 'square':
-            self.dots_frame.hide()
-            self.squares_frame.show()
-            self.lines_frame.hide()
-            self.app.inform.emit(_("Squares grid fill selected."))
-        else:
-            if self.reference_radio.get_value() != 'itself':
-                self.reference_radio.set_value('itself')
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Lines Grid works only for 'itself' reference ..."))
-            self.dots_frame.hide()
-            self.squares_frame.hide()
-            self.lines_frame.show()
+        # #####################################################################
+        # ####### Read the parameters #########################################
+        # #####################################################################
 
-    def add_robber_bar(self):
-        rb_margin = self.rb_margin_entry.get_value()
-        self.rb_thickness = self.rb_thickness_entry.get_value()
+        log.debug("Copper Thieving Tool started. Reading parameters.")
+        self.app.inform.emit(_("Copper Thieving Tool started. Reading parameters."))
 
-        # get the Gerber object on which the Robber bar will be inserted
-        selection_index = self.grb_object_combo.currentIndex()
-        model_index = self.app.collection.index(selection_index, 0, self.grb_object_combo.rootModelIndex())
+        ref_selected = self.ui.reference_radio.get_value()
+        if c_val is None:
+            c_val = float(self.app.defaults["tools_copperfill_clearance"])
+        if margin is None:
+            margin = float(self.app.defaults["tools_copperfill_margin"])
 
-        try:
-            self.grb_object = model_index.internalPointer().obj
-        except Exception as e:
-            log.debug("ToolCopperThieving.add_robber_bar() --> %s" % str(e))
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
-            return 'fail'
+        fill_type = self.ui.fill_type_radio.get_value()
+        dot_dia = self.ui.dot_dia_entry.get_value()
+        dot_spacing = self.ui.dot_spacing_entry.get_value()
+        square_size = self.ui.square_size_entry.get_value()
+        square_spacing = self.ui.squares_spacing_entry.get_value()
+        line_size = self.ui.line_size_entry.get_value()
+        line_spacing = self.ui.lines_spacing_entry.get_value()
 
-        try:
-            outline_pol = self.grb_object.solid_geometry.envelope
-        except TypeError:
-            outline_pol = MultiPolygon(self.grb_object.solid_geometry).envelope
+        # make sure that the source object solid geometry is an Iterable
+        if not isinstance(self.grb_object.solid_geometry, Iterable):
+            self.grb_object.solid_geometry = [self.grb_object.solid_geometry]
 
-        rb_distance = rb_margin + (self.rb_thickness / 2.0)
-        self.robber_line = outline_pol.buffer(rb_distance).exterior
+        def job_thread_thieving(app_obj):
+            # #########################################################################################
+            # Prepare isolation polygon. This will create the clearance over the Gerber features ######
+            # #########################################################################################
+            log.debug("Copper Thieving Tool. Preparing isolation polygons.")
+            app_obj.app.inform.emit(_("Copper Thieving Tool. Preparing isolation polygons."))
 
-        self.robber_geo = self.robber_line.buffer(self.rb_thickness / 2.0)
-
-        self.app.proc_container.update_view_text(' %s' % _("Append geometry"))
-
-        aperture_found = None
-        for ap_id, ap_val in self.grb_object.apertures.items():
-            if ap_val['type'] == 'C' and ap_val['size'] == self.rb_thickness:
-                aperture_found = ap_id
-                break
-
-        if aperture_found:
-            geo_elem = {}
-            geo_elem['solid'] = self.robber_geo
-            geo_elem['follow'] = self.robber_line
-            self.grb_object.apertures[aperture_found]['geometry'].append(deepcopy(geo_elem))
-        else:
-            ap_keys = list(self.grb_object.apertures.keys())
-            if ap_keys:
-                new_apid = str(int(max(ap_keys)) + 1)
-            else:
-                new_apid = '10'
-
-            self.grb_object.apertures[new_apid] = {}
-            self.grb_object.apertures[new_apid]['type'] = 'C'
-            self.grb_object.apertures[new_apid]['size'] = self.rb_thickness
-            self.grb_object.apertures[new_apid]['geometry'] = []
-
-            geo_elem = {}
-            geo_elem['solid'] = self.robber_geo
-            geo_elem['follow'] = self.robber_line
-            self.grb_object.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
-
-        geo_obj = self.grb_object.solid_geometry
-        if isinstance(geo_obj, MultiPolygon):
-            s_list = []
-            for pol in geo_obj.geoms:
-                s_list.append(pol)
-            s_list.append(self.robber_geo)
-            geo_obj = MultiPolygon(s_list)
-        elif isinstance(geo_obj, list):
-            geo_obj.append(self.robber_geo)
-        elif isinstance(geo_obj, Polygon):
-            geo_obj = MultiPolygon([geo_obj, self.robber_geo])
+            # variables to display the percentage of work done
+            geo_len = 0
+            try:
+                for pol in app_obj.grb_object.solid_geometry:
+                    geo_len += 1
+            except TypeError:
+                geo_len = 1
 
-        self.grb_object.solid_geometry = geo_obj
+            old_disp_number = 0
+            pol_nr = 0
 
-        self.app.proc_container.update_view_text(' %s' % _("Append source file"))
-        # update the source file with the new geometry:
-        self.grb_object.source_file = self.app.export_gerber(obj_name=self.grb_object.options['name'],
-                                                             filename=None,
-                                                             local_use=self.grb_object,
-                                                             use_thread=False)
-        self.app.proc_container.update_view_text(' %s' % '')
-        self.on_exit()
-        self.app.inform.emit('[success] %s' % _("Copper Thieving Tool done."))
+            clearance_geometry = []
+            try:
+                for pol in app_obj.grb_object.solid_geometry:
+                    if app_obj.app.abort_flag:
+                        # graceful abort requested by the user
+                        raise grace
 
-    def execute(self):
-        self.app.call_source = "copper_thieving_tool"
+                    clearance_geometry.append(
+                        pol.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4))
+                    )
 
-        self.clearance_val = self.clearance_entry.get_value()
-        self.margin_val = self.margin_entry.get_value()
-        reference_method = self.reference_radio.get_value()
+                    pol_nr += 1
+                    disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
 
-        # get the Gerber object on which the Copper thieving will be inserted
-        selection_index = self.grb_object_combo.currentIndex()
-        model_index = self.app.collection.index(selection_index, 0, self.grb_object_combo.rootModelIndex())
+                    if old_disp_number < disp_number <= 100:
+                        app_obj.app.proc_container.update_view_text(' %s ... %d%%' %
+                                                                    (_("Thieving"), int(disp_number)))
+                        old_disp_number = disp_number
+            except TypeError:
+                # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
+                # MultiPolygon (not an iterable)
+                clearance_geometry.append(
+                    app_obj.grb_object.solid_geometry.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4))
+                )
 
-        try:
-            self.grb_object = model_index.internalPointer().obj
-        except Exception as e:
-            log.debug("ToolCopperThieving.execute() --> %s" % str(e))
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
-            return 'fail'
+            app_obj.app.proc_container.update_view_text(' %s ...' % _("Buffering"))
+            clearance_geometry = unary_union(clearance_geometry)
 
-        if reference_method == 'itself':
-            bound_obj_name = self.grb_object_combo.currentText()
+            # #########################################################################################
+            # Prepare the area to fill with copper. ###################################################
+            # #########################################################################################
+            log.debug("Copper Thieving Tool. Preparing areas to fill with copper.")
+            app_obj.app.inform.emit(_("Copper Thieving Tool. Preparing areas to fill with copper."))
 
-            # Get reference object.
             try:
-                self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
+                if ref_obj is None or ref_obj == 'itself':
+                    working_obj = thieving_obj
+                else:
+                    working_obj = ref_obj
             except Exception as e:
-                self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(e)))
-                return "Could not retrieve object: %s" % self.obj_name
+                log.debug("ToolCopperThieving.on_copper_thieving() --> %s" % str(e))
+                return 'fail'
 
-            self.on_copper_thieving(
-                thieving_obj=self.grb_object,
-                c_val=self.clearance_val,
-                margin=self.margin_val
-            )
+            app_obj.app.proc_container.update_view_text(' %s' % _("Working..."))
+            if ref_selected == 'itself':
+                geo_n = working_obj.solid_geometry
 
-        elif reference_method == 'area':
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
+                try:
+                    if app_obj.ui.bbox_type_radio.get_value() == 'min':
+                        if isinstance(geo_n, MultiPolygon):
+                            env_obj = geo_n.convex_hull
+                        elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
+                                (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
+                            env_obj = cascaded_union(geo_n)
+                        else:
+                            env_obj = cascaded_union(geo_n)
+                            env_obj = env_obj.convex_hull
+                        bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
+                    else:
+                        if isinstance(geo_n, Polygon):
+                            bounding_box = geo_n.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre).exterior
+                        elif isinstance(geo_n, list):
+                            geo_n = unary_union(geo_n)
+                            bounding_box = geo_n.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre).exterior
+                        elif isinstance(geo_n, MultiPolygon):
+                            x0, y0, x1, y1 = geo_n.bounds
+                            geo = box(x0, y0, x1, y1)
+                            bounding_box = geo.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
+                        else:
+                            app_obj.app.inform.emit(
+                                '[ERROR_NOTCL] %s: %s' % (_("Geometry not supported for bounding box"), type(geo_n))
+                            )
+                            return 'fail'
 
-            self.area_method = True
+                except Exception as e:
+                    log.debug("ToolCopperFIll.on_copper_thieving()  'itself'  --> %s" % str(e))
+                    app_obj.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available."))
+                    return 'fail'
+            elif ref_selected == 'area':
+                geo_buff_list = []
+                try:
+                    for poly in working_obj:
+                        if app_obj.app.abort_flag:
+                            # graceful abort requested by the user
+                            raise grace
+                        geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
+                except TypeError:
+                    geo_buff_list.append(working_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
 
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.app.mp)
-                self.app.plotcanvas.graph_event_disconnect(self.app.mm)
-                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+                bounding_box = MultiPolygon(geo_buff_list)
+            else:   # ref_selected == 'box'
+                geo_n = working_obj.solid_geometry
 
-            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
-            self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
+                if working_obj.kind == 'geometry':
+                    try:
+                        __ = iter(geo_n)
+                    except Exception as e:
+                        log.debug("ToolCopperFIll.on_copper_thieving() 'box' --> %s" % str(e))
+                        geo_n = [geo_n]
 
-        elif reference_method == 'box':
-            bound_obj_name = self.ref_combo.currentText()
+                    geo_buff_list = []
+                    for poly in geo_n:
+                        if app_obj.app.abort_flag:
+                            # graceful abort requested by the user
+                            raise grace
+                        geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
 
-            # Get reference object.
-            try:
-                self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
-            except Exception as e:
-                self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), bound_obj_name))
-                return "Could not retrieve object: %s. Error: %s" % (bound_obj_name, str(e))
+                    bounding_box = cascaded_union(geo_buff_list)
+                elif working_obj.kind == 'gerber':
+                    geo_n = cascaded_union(geo_n).convex_hull
+                    bounding_box = cascaded_union(thieving_obj.solid_geometry).convex_hull.intersection(geo_n)
+                    bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
+                else:
+                    app_obj.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported."))
+                    return 'fail'
 
-            self.on_copper_thieving(
-                thieving_obj=self.grb_object,
-                ref_obj=self.ref_obj,
-                c_val=self.clearance_val,
-                margin=self.margin_val
-            )
+            log.debug("Copper Thieving Tool. Finished creating areas to fill with copper.")
 
-        # To be called after clicking on the plot.
+            app_obj.app.inform.emit(_("Copper Thieving Tool. Appending new geometry and buffering."))
 
-    def on_mouse_release(self, event):
-        if self.app.is_legacy is False:
-            event_pos = event.pos
-            # event_is_dragging = event.is_dragging
-            right_button = 2
-        else:
-            event_pos = (event.xdata, event.ydata)
-            # event_is_dragging = self.app.plotcanvas.is_dragging
-            right_button = 3
+            # #########################################################################################
+            # ########## Generate filling geometry. ###################################################
+            # #########################################################################################
 
-        event_pos = self.app.plotcanvas.translate_coords(event_pos)
+            app_obj.new_solid_geometry = bounding_box.difference(clearance_geometry)
 
-        # do clear area only for left mouse clicks
-        if event.button == 1:
-            if self.first_click is False:
-                self.first_click = True
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the filling area."))
+            # determine the bounding box polygon for the entire Gerber object to which we add copper thieving
+            # if isinstance(geo_n, list):
+            #     env_obj = unary_union(geo_n).buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
+            # else:
+            #     env_obj = geo_n.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
+            #
+            # x0, y0, x1, y1 = env_obj.bounds
+            # bounding_box = box(x0, y0, x1, y1)
+            app_obj.app.proc_container.update_view_text(' %s' % _("Create geometry"))
 
-                self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
-                if self.app.grid_status() is True:
-                    self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
-            else:
-                self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
-                self.app.delete_selection_shape()
+            bounding_box = thieving_obj.solid_geometry.envelope.buffer(
+                distance=margin,
+                join_style=base.JOIN_STYLE.mitre
+            )
+            x0, y0, x1, y1 = bounding_box.bounds
 
-                if self.app.grid_status() is True:
-                    curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
-                else:
-                    curr_pos = (event_pos[0], event_pos[1])
+            if fill_type == 'dot' or fill_type == 'square':
+                # build the MultiPolygon of dots/squares that will fill the entire bounding box
+                thieving_list = []
 
-                x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
-                x1, y1 = curr_pos[0], curr_pos[1]
-                pt1 = (x0, y0)
-                pt2 = (x1, y0)
-                pt3 = (x1, y1)
-                pt4 = (x0, y1)
+                if fill_type == 'dot':
+                    radius = dot_dia / 2.0
+                    new_x = x0 + radius
+                    new_y = y0 + radius
+                    while new_x <= x1 - radius:
+                        while new_y <= y1 - radius:
+                            dot_geo = Point((new_x, new_y)).buffer(radius, resolution=64)
+                            thieving_list.append(dot_geo)
+                            new_y += dot_dia + dot_spacing
+                        new_x += dot_dia + dot_spacing
+                        new_y = y0 + radius
+                else:
+                    h_size = square_size / 2.0
+                    new_x = x0 + h_size
+                    new_y = y0 + h_size
+                    while new_x <= x1 - h_size:
+                        while new_y <= y1 - h_size:
+                            a, b, c, d = (Point((new_x, new_y)).buffer(h_size)).bounds
+                            square_geo = box(a, b, c, d)
+                            thieving_list.append(square_geo)
+                            new_y += square_size + square_spacing
+                        new_x += square_size + square_spacing
+                        new_y = y0 + h_size
 
-                new_rectangle = Polygon([pt1, pt2, pt3, pt4])
-                self.sel_rect.append(new_rectangle)
+                thieving_box_geo = MultiPolygon(thieving_list)
+                dx = bounding_box.centroid.x - thieving_box_geo.centroid.x
+                dy = bounding_box.centroid.y - thieving_box_geo.centroid.y
 
-                # add a temporary shape on canvas
-                self.draw_tool_selection_shape(old_coords=(x0, y0), coords=(x1, y1))
-                self.first_click = False
-                return
+                thieving_box_geo = affinity.translate(thieving_box_geo, xoff=dx, yoff=dy)
 
-        elif event.button == right_button and self.mouse_is_dragging is False:
-            self.area_method = False
-            self.first_click = False
+                try:
+                    _it = iter(app_obj.new_solid_geometry)
+                except TypeError:
+                    app_obj.new_solid_geometry = [app_obj.new_solid_geometry]
 
-            self.delete_tool_selection_shape()
+                try:
+                    _it = iter(thieving_box_geo)
+                except TypeError:
+                    thieving_box_geo = [thieving_box_geo]
 
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
-                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.mr)
-                self.app.plotcanvas.graph_event_disconnect(self.mm)
+                thieving_geo = []
+                for dot_geo in thieving_box_geo:
+                    for geo_t in app_obj.new_solid_geometry:
+                        if dot_geo.within(geo_t):
+                            thieving_geo.append(dot_geo)
 
-            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
-                                                                  self.app.on_mouse_click_over_plot)
-            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
-                                                                  self.app.on_mouse_move_over_plot)
-            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                                  self.app.on_mouse_click_release_over_plot)
+                app_obj.new_solid_geometry = thieving_geo
 
-            if len(self.sel_rect) == 0:
-                return
+            if fill_type == 'line':
+                half_thick_line = line_size / 2.0
 
-            self.sel_rect = cascaded_union(self.sel_rect)
+                # create a thick polygon-line that surrounds the copper features
+                outline_geometry = []
+                try:
+                    for pol in app_obj.grb_object.solid_geometry:
+                        if app_obj.app.abort_flag:
+                            # graceful abort requested by the user
+                            raise grace
 
-            if not isinstance(self.sel_rect, Iterable):
-                self.sel_rect = [self.sel_rect]
+                        outline_geometry.append(
+                            pol.buffer(c_val+half_thick_line, int(int(app_obj.geo_steps_per_circle) / 4))
+                        )
 
-            self.on_copper_thieving(
-                thieving_obj=self.grb_object,
-                ref_obj=self.sel_rect,
-                c_val=self.clearance_val,
-                margin=self.margin_val
-            )
+                        pol_nr += 1
+                        disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
 
-    # called on mouse move
-    def on_mouse_move(self, event):
-        if self.app.is_legacy is False:
-            event_pos = event.pos
-            event_is_dragging = event.is_dragging
-            # right_button = 2
-        else:
-            event_pos = (event.xdata, event.ydata)
-            event_is_dragging = self.app.plotcanvas.is_dragging
-            # right_button = 3
+                        if old_disp_number < disp_number <= 100:
+                            app_obj.app.proc_container.update_view_text(' %s ... %d%%' %
+                                                                        (_("Buffering"), int(disp_number)))
+                            old_disp_number = disp_number
+                except TypeError:
+                    # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
+                    # MultiPolygon (not an iterable)
+                    outline_geometry.append(
+                        app_obj.grb_object.solid_geometry.buffer(
+                            c_val+half_thick_line,
+                            int(int(app_obj.geo_steps_per_circle) / 4)
+                        )
+                    )
 
-        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+                app_obj.app.proc_container.update_view_text(' %s' % _("Buffering"))
+                outline_geometry = unary_union(outline_geometry)
 
-        # detect mouse dragging motion
-        if event_is_dragging is True:
-            self.mouse_is_dragging = True
-        else:
-            self.mouse_is_dragging = False
+                outline_line = []
+                try:
+                    for geo_o in outline_geometry:
+                        outline_line.append(
+                            geo_o.exterior.buffer(
+                                half_thick_line, resolution=int(int(app_obj.geo_steps_per_circle) / 4)
+                            )
+                        )
+                except TypeError:
+                    outline_line.append(
+                        outline_geometry.exterior.buffer(
+                            half_thick_line, resolution=int(int(app_obj.geo_steps_per_circle) / 4)
+                        )
+                    )
 
-        # update the cursor position
-        if self.app.grid_status() is True:
-            # Update cursor
-            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+                outline_geometry = unary_union(outline_line)
 
-            self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
-                                         symbol='++', edge_color=self.app.cursor_color_3D,
-                                         edge_width=self.app.defaults["global_cursor_width"],
-                                         size=self.app.defaults["global_cursor_size"])
+                # create a polygon-line that surrounds in the inside the bounding box polygon of the target Gerber
+                box_outline_geo = box(x0, y0, x1, y1).buffer(-half_thick_line)
+                box_outline_geo_exterior = box_outline_geo.exterior
+                box_outline_geometry = box_outline_geo_exterior.buffer(
+                    half_thick_line,
+                    resolution=int(int(app_obj.geo_steps_per_circle) / 4)
+                )
 
-        if self.cursor_pos is None:
-            self.cursor_pos = (0, 0)
+                bx0, by0, bx1, by1 = box_outline_geo.bounds
+                thieving_lines_geo = []
+                new_x = bx0
+                new_y = by0
+                while new_x <= x1 - half_thick_line:
+                    line_geo = LineString([(new_x, by0), (new_x, by1)]).buffer(
+                        half_thick_line,
+                        resolution=int(int(app_obj.geo_steps_per_circle) / 4)
+                    )
+                    thieving_lines_geo.append(line_geo)
+                    new_x += line_size + line_spacing
 
-        self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
-        self.app.dy = curr_pos[1] - float(self.cursor_pos[1])
+                while new_y <= y1 - half_thick_line:
+                    line_geo = LineString([(bx0, new_y), (bx1, new_y)]).buffer(
+                        half_thick_line,
+                        resolution=int(int(app_obj.geo_steps_per_circle) / 4)
+                    )
+                    thieving_lines_geo.append(line_geo)
+                    new_y += line_size + line_spacing
 
-        # # update the positions on status bar
-        self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                           "<b>Y</b>: %.4f&nbsp;" % (curr_pos[0], curr_pos[1]))
-        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+                # merge everything together
+                diff_lines_geo = []
+                for line_poly in thieving_lines_geo:
+                    rest_line = line_poly.difference(clearance_geometry)
+                    diff_lines_geo.append(rest_line)
+                app_obj.flatten([outline_geometry, box_outline_geometry, diff_lines_geo])
+                app_obj.new_solid_geometry = app_obj.flat_geometry
 
-        units = self.app.defaults["units"].lower()
-        self.app.plotcanvas.text_hud.text = \
-            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
-                self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
+            app_obj.app.proc_container.update_view_text(' %s' % _("Append geometry"))
+            geo_list = app_obj.grb_object.solid_geometry
+            if isinstance(app_obj.grb_object.solid_geometry, MultiPolygon):
+                geo_list = list(app_obj.grb_object.solid_geometry.geoms)
 
-        # draw the utility geometry
-        if self.first_click:
-            self.app.delete_selection_shape()
-            self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
-                                                 coords=(curr_pos[0], curr_pos[1]))
+            if '0' not in app_obj.grb_object.apertures:
+                app_obj.grb_object.apertures['0'] = {}
+                app_obj.grb_object.apertures['0']['geometry'] = []
+                app_obj.grb_object.apertures['0']['type'] = 'REG'
+                app_obj.grb_object.apertures['0']['size'] = 0.0
 
-    def on_copper_thieving(self, thieving_obj, ref_obj=None, c_val=None, margin=None, run_threaded=True):
-        """
+            try:
+                for poly in app_obj.new_solid_geometry:
+                    # append to the new solid geometry
+                    geo_list.append(poly)
 
-        :param thieving_obj:
-        :param ref_obj:
-        :param c_val:
-        :param margin:
-        :param run_threaded:
-        :return:
-        """
+                    # append into the '0' aperture
+                    geo_elem = {}
+                    geo_elem['solid'] = poly
+                    geo_elem['follow'] = poly.exterior
+                    app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
+            except TypeError:
+                # append to the new solid geometry
+                geo_list.append(app_obj.new_solid_geometry)
+
+                # append into the '0' aperture
+                geo_elem = {}
+                geo_elem['solid'] = app_obj.new_solid_geometry
+                geo_elem['follow'] = app_obj.new_solid_geometry.exterior
+                app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
+
+            app_obj.grb_object.solid_geometry = MultiPolygon(geo_list).buffer(0.0000001).buffer(-0.0000001)
+
+            app_obj.app.proc_container.update_view_text(' %s' % _("Append source file"))
+            # update the source file with the new geometry:
+            app_obj.grb_object.source_file = app_obj.app.export_gerber(obj_name=app_obj.grb_object.options['name'],
+                                                                       filename=None,
+                                                                       local_use=app_obj.grb_object,
+                                                                       use_thread=False)
+            app_obj.app.proc_container.update_view_text(' %s' % '')
+            app_obj.on_exit()
+            app_obj.app.inform.emit('[success] %s' % _("Copper Thieving Tool done."))
 
         if run_threaded:
-            proc = self.app.proc_container.new('%s ...' % _("Thieving"))
+            self.app.worker_task.emit({'fcn': job_thread_thieving, 'params': [self]})
         else:
-            QtWidgets.QApplication.processEvents()
+            job_thread_thieving(self)
 
-        self.app.proc_container.view.set_busy('%s ...' % _("Thieving"))
+    def on_add_ppm(self):
+        run_threaded = True
 
-        # #####################################################################
-        # ####### Read the parameters #########################################
-        # #####################################################################
+        if run_threaded:
+            self.app.proc_container.new('%s ...' % _("P-Plating Mask"))
+        else:
+            QtWidgets.QApplication.processEvents()
 
-        log.debug("Copper Thieving Tool started. Reading parameters.")
-        self.app.inform.emit(_("Copper Thieving Tool started. Reading parameters."))
-
-        ref_selected = self.reference_radio.get_value()
-        if c_val is None:
-            c_val = float(self.app.defaults["tools_copperfill_clearance"])
-        if margin is None:
-            margin = float(self.app.defaults["tools_copperfill_margin"])
-
-        fill_type = self.fill_type_radio.get_value()
-        dot_dia = self.dot_dia_entry.get_value()
-        dot_spacing = self.dot_spacing_entry.get_value()
-        square_size = self.square_size_entry.get_value()
-        square_spacing = self.squares_spacing_entry.get_value()
-        line_size = self.line_size_entry.get_value()
-        line_spacing = self.lines_spacing_entry.get_value()
-
-        # make sure that the source object solid geometry is an Iterable
-        if not isinstance(self.grb_object.solid_geometry, Iterable):
-            self.grb_object.solid_geometry = [self.grb_object.solid_geometry]
+        self.app.proc_container.view.set_busy('%s ...' % _("P-Plating Mask"))
 
-        def job_thread_thieving(app_obj):
-            # #########################################################################################
-            # Prepare isolation polygon. This will create the clearance over the Gerber features ######
-            # #########################################################################################
-            log.debug("Copper Thieving Tool. Preparing isolation polygons.")
-            app_obj.app.inform.emit(_("Copper Thieving Tool. Preparing isolation polygons."))
+        if run_threaded:
+            self.app.worker_task.emit({'fcn': self.on_new_pattern_plating_object, 'params': []})
+        else:
+            self.on_new_pattern_plating_object()
 
-            # variables to display the percentage of work done
-            geo_len = 0
-            try:
-                for pol in app_obj.grb_object.solid_geometry:
-                    geo_len += 1
-            except TypeError:
-                geo_len = 1
+    def on_new_pattern_plating_object(self):
+        # get the Gerber object on which the Copper thieving will be inserted
+        selection_index = self.ui.sm_object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.ui.sm_object_combo.rootModelIndex())
 
-            old_disp_number = 0
-            pol_nr = 0
+        try:
+            self.sm_object = model_index.internalPointer().obj
+        except Exception as e:
+            log.debug("ToolCopperThieving.on_add_ppm() --> %s" % str(e))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
+            return
 
-            clearance_geometry = []
-            try:
-                for pol in app_obj.grb_object.solid_geometry:
-                    if app_obj.app.abort_flag:
-                        # graceful abort requested by the user
-                        raise grace
+        ppm_clearance = self.ui.clearance_ppm_entry.get_value()
+        rb_thickness = self.rb_thickness
 
-                    clearance_geometry.append(
-                        pol.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4))
-                    )
+        self.app.proc_container.update_view_text(' %s' % _("Append PP-M geometry"))
+        geo_list = self.sm_object.solid_geometry
+        if isinstance(self.sm_object.solid_geometry, MultiPolygon):
+            geo_list = list(self.sm_object.solid_geometry.geoms)
 
-                    pol_nr += 1
-                    disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
+        # if the clearance is negative apply it to the original soldermask too
+        if ppm_clearance < 0:
+            temp_geo_list = []
+            for geo in geo_list:
+                temp_geo_list.append(geo.buffer(ppm_clearance))
+            geo_list = temp_geo_list
 
-                    if old_disp_number < disp_number <= 100:
-                        app_obj.app.proc_container.update_view_text(' %s ... %d%%' %
-                                                                    (_("Thieving"), int(disp_number)))
-                        old_disp_number = disp_number
-            except TypeError:
-                # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
-                # MultiPolygon (not an iterable)
-                clearance_geometry.append(
-                    app_obj.grb_object.solid_geometry.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4))
-                )
+        plated_area = 0.0
+        for geo in geo_list:
+            plated_area += geo.area
 
-            app_obj.app.proc_container.update_view_text(' %s ...' % _("Buffering"))
-            clearance_geometry = unary_union(clearance_geometry)
+        if self.new_solid_geometry:
+            for geo in self.new_solid_geometry:
+                plated_area += geo.area
+        if self.robber_geo:
+            plated_area += self.robber_geo.area
+        self.ui.plated_area_entry.set_value(plated_area)
 
-            # #########################################################################################
-            # Prepare the area to fill with copper. ###################################################
-            # #########################################################################################
-            log.debug("Copper Thieving Tool. Preparing areas to fill with copper.")
-            app_obj.app.inform.emit(_("Copper Thieving Tool. Preparing areas to fill with copper."))
+        thieving_solid_geo = self.new_solid_geometry
+        robber_solid_geo = self.robber_geo
+        robber_line = self.robber_line
 
-            try:
-                if ref_obj is None or ref_obj == 'itself':
-                    working_obj = thieving_obj
-                else:
-                    working_obj = ref_obj
-            except Exception as e:
-                log.debug("ToolCopperThieving.on_copper_thieving() --> %s" % str(e))
-                return 'fail'
+        def obj_init(grb_obj, app_obj):
+            grb_obj.multitool = False
+            grb_obj.source_file = []
+            grb_obj.multigeo = False
+            grb_obj.follow = False
+            grb_obj.apertures = {}
+            grb_obj.solid_geometry = []
 
-            app_obj.app.proc_container.update_view_text(' %s' % _("Working..."))
-            if ref_selected == 'itself':
-                geo_n = working_obj.solid_geometry
+            # if we have copper thieving geometry, add it
+            if thieving_solid_geo:
+                if '0' not in grb_obj.apertures:
+                    grb_obj.apertures['0'] = {}
+                    grb_obj.apertures['0']['geometry'] = []
+                    grb_obj.apertures['0']['type'] = 'REG'
+                    grb_obj.apertures['0']['size'] = 0.0
 
                 try:
-                    if app_obj.bbox_type_radio.get_value() == 'min':
-                        if isinstance(geo_n, MultiPolygon):
-                            env_obj = geo_n.convex_hull
-                        elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
-                                (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
-                            env_obj = cascaded_union(geo_n)
-                        else:
-                            env_obj = cascaded_union(geo_n)
-                            env_obj = env_obj.convex_hull
-                        bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
-                    else:
-                        if isinstance(geo_n, Polygon):
-                            bounding_box = geo_n.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre).exterior
-                        elif isinstance(geo_n, list):
-                            geo_n = unary_union(geo_n)
-                            bounding_box = geo_n.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre).exterior
-                        elif isinstance(geo_n, MultiPolygon):
-                            x0, y0, x1, y1 = geo_n.bounds
-                            geo = box(x0, y0, x1, y1)
-                            bounding_box = geo.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
-                        else:
-                            app_obj.app.inform.emit(
-                                '[ERROR_NOTCL] %s: %s' % (_("Geometry not supported for bounding box"), type(geo_n))
-                            )
-                            return 'fail'
+                    for poly in thieving_solid_geo:
+                        poly_b = poly.buffer(ppm_clearance)
 
-                except Exception as e:
-                    log.debug("ToolCopperFIll.on_copper_thieving()  'itself'  --> %s" % str(e))
-                    app_obj.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available."))
-                    return 'fail'
-            elif ref_selected == 'area':
-                geo_buff_list = []
-                try:
-                    for poly in working_obj:
-                        if app_obj.app.abort_flag:
-                            # graceful abort requested by the user
-                            raise grace
-                        geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
-                except TypeError:
-                    geo_buff_list.append(working_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
+                        # append to the new solid geometry
+                        geo_list.append(poly_b)
 
-                bounding_box = MultiPolygon(geo_buff_list)
-            else:   # ref_selected == 'box'
-                geo_n = working_obj.solid_geometry
+                        # append into the '0' aperture
+                        geo_elem = {}
+                        geo_elem['solid'] = poly_b
+                        geo_elem['follow'] = poly_b.exterior
+                        grb_obj.apertures['0']['geometry'].append(deepcopy(geo_elem))
+                except TypeError:
+                    # append to the new solid geometry
+                    geo_list.append(thieving_solid_geo.buffer(ppm_clearance))
 
-                if working_obj.kind == 'geometry':
-                    try:
-                        __ = iter(geo_n)
-                    except Exception as e:
-                        log.debug("ToolCopperFIll.on_copper_thieving() 'box' --> %s" % str(e))
-                        geo_n = [geo_n]
+                    # append into the '0' aperture
+                    geo_elem = {}
+                    geo_elem['solid'] = thieving_solid_geo.buffer(ppm_clearance)
+                    geo_elem['follow'] = thieving_solid_geo.buffer(ppm_clearance).exterior
+                    grb_obj.apertures['0']['geometry'].append(deepcopy(geo_elem))
 
-                    geo_buff_list = []
-                    for poly in geo_n:
-                        if app_obj.app.abort_flag:
-                            # graceful abort requested by the user
-                            raise grace
-                        geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
+            # if we have robber bar geometry, add it
+            if robber_solid_geo:
+                aperture_found = None
+                for ap_id, ap_val in grb_obj.apertures.items():
+                    if ap_val['type'] == 'C' and ap_val['size'] == app_obj.rb_thickness + ppm_clearance:
+                        aperture_found = ap_id
+                        break
 
-                    bounding_box = cascaded_union(geo_buff_list)
-                elif working_obj.kind == 'gerber':
-                    geo_n = cascaded_union(geo_n).convex_hull
-                    bounding_box = cascaded_union(thieving_obj.solid_geometry).convex_hull.intersection(geo_n)
-                    bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
+                if aperture_found:
+                    geo_elem = {}
+                    geo_elem['solid'] = robber_solid_geo
+                    geo_elem['follow'] = robber_line
+                    grb_obj.apertures[aperture_found]['geometry'].append(deepcopy(geo_elem))
                 else:
-                    app_obj.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported."))
-                    return 'fail'
+                    ap_keys = list(grb_obj.apertures.keys())
+                    max_apid = int(max(ap_keys))
+                    if ap_keys and max_apid != 0:
+                        new_apid = str(max_apid + 1)
+                    else:
+                        new_apid = '10'
 
-            log.debug("Copper Thieving Tool. Finished creating areas to fill with copper.")
+                    grb_obj.apertures[new_apid] = {}
+                    grb_obj.apertures[new_apid]['type'] = 'C'
+                    grb_obj.apertures[new_apid]['size'] = rb_thickness + ppm_clearance
+                    grb_obj.apertures[new_apid]['geometry'] = []
 
-            app_obj.app.inform.emit(_("Copper Thieving Tool. Appending new geometry and buffering."))
+                    geo_elem = {}
+                    geo_elem['solid'] = robber_solid_geo.buffer(ppm_clearance)
+                    geo_elem['follow'] = Polygon(robber_line).buffer(ppm_clearance / 2.0).exterior
+                    grb_obj.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
 
-            # #########################################################################################
-            # ########## Generate filling geometry. ###################################################
-            # #########################################################################################
+                geo_list.append(robber_solid_geo.buffer(ppm_clearance))
 
-            app_obj.new_solid_geometry = bounding_box.difference(clearance_geometry)
+            grb_obj.solid_geometry = MultiPolygon(geo_list).buffer(0.0000001).buffer(-0.0000001)
 
-            # determine the bounding box polygon for the entire Gerber object to which we add copper thieving
-            # if isinstance(geo_n, list):
-            #     env_obj = unary_union(geo_n).buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
-            # else:
-            #     env_obj = geo_n.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
-            #
-            # x0, y0, x1, y1 = env_obj.bounds
-            # bounding_box = box(x0, y0, x1, y1)
-            app_obj.app.proc_container.update_view_text(' %s' % _("Create geometry"))
+            app_obj.proc_container.update_view_text(' %s' % _("Append source file"))
+            # update the source file with the new geometry:
+            grb_obj.source_file = app_obj.export_gerber(obj_name=name,
+                                                        filename=None,
+                                                        local_use=grb_obj,
+                                                        use_thread=False)
+            app_obj.proc_container.update_view_text(' %s' % '')
 
-            bounding_box = thieving_obj.solid_geometry.envelope.buffer(
-                distance=margin,
-                join_style=base.JOIN_STYLE.mitre
-            )
-            x0, y0, x1, y1 = bounding_box.bounds
+        # Object name
+        obj_name, separatpr, obj_extension = self.sm_object.options['name'].rpartition('.')
+        name = '%s_%s.%s' % (obj_name, 'plating_mask', obj_extension)
 
-            if fill_type == 'dot' or fill_type == 'square':
-                # build the MultiPolygon of dots/squares that will fill the entire bounding box
-                thieving_list = []
+        self.app.app_obj.new_object('gerber', name, obj_init, autoselected=False)
 
-                if fill_type == 'dot':
-                    radius = dot_dia / 2.0
-                    new_x = x0 + radius
-                    new_y = y0 + radius
-                    while new_x <= x1 - radius:
-                        while new_y <= y1 - radius:
-                            dot_geo = Point((new_x, new_y)).buffer(radius, resolution=64)
-                            thieving_list.append(dot_geo)
-                            new_y += dot_dia + dot_spacing
-                        new_x += dot_dia + dot_spacing
-                        new_y = y0 + radius
-                else:
-                    h_size = square_size / 2.0
-                    new_x = x0 + h_size
-                    new_y = y0 + h_size
-                    while new_x <= x1 - h_size:
-                        while new_y <= y1 - h_size:
-                            a, b, c, d = (Point((new_x, new_y)).buffer(h_size)).bounds
-                            square_geo = box(a, b, c, d)
-                            thieving_list.append(square_geo)
-                            new_y += square_size + square_spacing
-                        new_x += square_size + square_spacing
-                        new_y = y0 + h_size
+        # Register recent file
+        self.app.file_opened.emit("gerber", name)
 
-                thieving_box_geo = MultiPolygon(thieving_list)
-                dx = bounding_box.centroid.x - thieving_box_geo.centroid.x
-                dy = bounding_box.centroid.y - thieving_box_geo.centroid.y
+        self.on_exit()
+        self.app.inform.emit('[success] %s' % _("Generating Pattern Plating Mask done."))
 
-                thieving_box_geo = affinity.translate(thieving_box_geo, xoff=dx, yoff=dy)
+    def replot(self, obj):
+        def worker_task():
+            with self.app.proc_container.new('%s...' % _("Plotting")):
+                obj.plot()
 
-                try:
-                    _it = iter(app_obj.new_solid_geometry)
-                except TypeError:
-                    app_obj.new_solid_geometry = [app_obj.new_solid_geometry]
+        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
 
-                try:
-                    _it = iter(thieving_box_geo)
-                except TypeError:
-                    thieving_box_geo = [thieving_box_geo]
+    def on_exit(self):
+        # plot the objects
+        if self.grb_object:
+            self.replot(obj=self.grb_object)
 
-                thieving_geo = []
-                for dot_geo in thieving_box_geo:
-                    for geo_t in app_obj.new_solid_geometry:
-                        if dot_geo.within(geo_t):
-                            thieving_geo.append(dot_geo)
+        if self.sm_object:
+            self.replot(obj=self.sm_object)
 
-                app_obj.new_solid_geometry = thieving_geo
+        # update the bounding box values
+        try:
+            a, b, c, d = self.grb_object.bounds()
+            self.grb_object.options['xmin'] = a
+            self.grb_object.options['ymin'] = b
+            self.grb_object.options['xmax'] = c
+            self.grb_object.options['ymax'] = d
+        except Exception as e:
+            log.debug("ToolCopperThieving.on_exit() bounds -> copper thieving Gerber error --> %s" % str(e))
 
-            if fill_type == 'line':
-                half_thick_line = line_size / 2.0
+        # update the bounding box values
+        try:
+            a, b, c, d = self.sm_object.bounds()
+            self.sm_object.options['xmin'] = a
+            self.sm_object.options['ymin'] = b
+            self.sm_object.options['xmax'] = c
+            self.sm_object.options['ymax'] = d
+        except Exception as e:
+            log.debug("ToolCopperThieving.on_exit() bounds -> pattern plating mask error --> %s" % str(e))
 
-                # create a thick polygon-line that surrounds the copper features
-                outline_geometry = []
-                try:
-                    for pol in app_obj.grb_object.solid_geometry:
-                        if app_obj.app.abort_flag:
-                            # graceful abort requested by the user
-                            raise grace
+        # reset the variables
+        self.grb_object = None
+        self.sm_object = None
+        self.ref_obj = None
+        self.sel_rect = []
 
-                        outline_geometry.append(
-                            pol.buffer(c_val+half_thick_line, int(int(app_obj.geo_steps_per_circle) / 4))
-                        )
+        # Events ID
+        self.mr = None
+        self.mm = None
 
-                        pol_nr += 1
-                        disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
+        # Mouse cursor positions
+        self.mouse_is_dragging = False
+        self.cursor_pos = (0, 0)
+        self.first_click = False
 
-                        if old_disp_number < disp_number <= 100:
-                            app_obj.app.proc_container.update_view_text(' %s ... %d%%' %
-                                                                        (_("Buffering"), int(disp_number)))
-                            old_disp_number = disp_number
-                except TypeError:
-                    # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
-                    # MultiPolygon (not an iterable)
-                    outline_geometry.append(
-                        app_obj.grb_object.solid_geometry.buffer(
-                            c_val+half_thick_line,
-                            int(int(app_obj.geo_steps_per_circle) / 4)
-                        )
-                    )
+        # if True it means we exited from tool in the middle of area adding therefore disconnect the events
+        if self.area_method is True:
+            self.app.delete_selection_shape()
+            self.area_method = False
 
-                app_obj.app.proc_container.update_view_text(' %s' % _("Buffering"))
-                outline_geometry = unary_union(outline_geometry)
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
 
-                outline_line = []
-                try:
-                    for geo_o in outline_geometry:
-                        outline_line.append(
-                            geo_o.exterior.buffer(
-                                half_thick_line, resolution=int(int(app_obj.geo_steps_per_circle) / 4)
-                            )
-                        )
-                except TypeError:
-                    outline_line.append(
-                        outline_geometry.exterior.buffer(
-                            half_thick_line, resolution=int(int(app_obj.geo_steps_per_circle) / 4)
-                        )
-                    )
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+                                                                  self.app.on_mouse_click_over_plot)
+            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
+                                                                  self.app.on_mouse_move_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
 
-                outline_geometry = unary_union(outline_line)
+        self.app.call_source = "app"
+        self.app.inform.emit('[success] %s' % _("Copper Thieving Tool exit."))
 
-                # create a polygon-line that surrounds in the inside the bounding box polygon of the target Gerber
-                box_outline_geo = box(x0, y0, x1, y1).buffer(-half_thick_line)
-                box_outline_geo_exterior = box_outline_geo.exterior
-                box_outline_geometry = box_outline_geo_exterior.buffer(
-                    half_thick_line,
-                    resolution=int(int(app_obj.geo_steps_per_circle) / 4)
-                )
+    def flatten(self, geometry):
+        """
+        Creates a list of non-iterable linear geometry objects.
+        :param geometry: Shapely type or list or list of list of such.
 
-                bx0, by0, bx1, by1 = box_outline_geo.bounds
-                thieving_lines_geo = []
-                new_x = bx0
-                new_y = by0
-                while new_x <= x1 - half_thick_line:
-                    line_geo = LineString([(new_x, by0), (new_x, by1)]).buffer(
-                        half_thick_line,
-                        resolution=int(int(app_obj.geo_steps_per_circle) / 4)
-                    )
-                    thieving_lines_geo.append(line_geo)
-                    new_x += line_size + line_spacing
+        Results are placed in self.flat_geometry
+        """
 
-                while new_y <= y1 - half_thick_line:
-                    line_geo = LineString([(bx0, new_y), (bx1, new_y)]).buffer(
-                        half_thick_line,
-                        resolution=int(int(app_obj.geo_steps_per_circle) / 4)
-                    )
-                    thieving_lines_geo.append(line_geo)
-                    new_y += line_size + line_spacing
+        # ## If iterable, expand recursively.
+        try:
+            for geo in geometry:
+                if geo is not None:
+                    self.flatten(geometry=geo)
 
-                # merge everything together
-                diff_lines_geo = []
-                for line_poly in thieving_lines_geo:
-                    rest_line = line_poly.difference(clearance_geometry)
-                    diff_lines_geo.append(rest_line)
-                app_obj.flatten([outline_geometry, box_outline_geometry, diff_lines_geo])
-                app_obj.new_solid_geometry = app_obj.flat_geometry
+        # ## Not iterable, do the actual indexing and add.
+        except TypeError:
+            self.flat_geometry.append(geometry)
 
-            app_obj.app.proc_container.update_view_text(' %s' % _("Append geometry"))
-            geo_list = app_obj.grb_object.solid_geometry
-            if isinstance(app_obj.grb_object.solid_geometry, MultiPolygon):
-                geo_list = list(app_obj.grb_object.solid_geometry.geoms)
+        return self.flat_geometry
 
-            if '0' not in app_obj.grb_object.apertures:
-                app_obj.grb_object.apertures['0'] = {}
-                app_obj.grb_object.apertures['0']['geometry'] = []
-                app_obj.grb_object.apertures['0']['type'] = 'REG'
-                app_obj.grb_object.apertures['0']['size'] = 0.0
 
-            try:
-                for poly in app_obj.new_solid_geometry:
-                    # append to the new solid geometry
-                    geo_list.append(poly)
+class ThievingUI:
 
-                    # append into the '0' aperture
-                    geo_elem = {}
-                    geo_elem['solid'] = poly
-                    geo_elem['follow'] = poly.exterior
-                    app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
-            except TypeError:
-                # append to the new solid geometry
-                geo_list.append(app_obj.new_solid_geometry)
+    toolName = _("Copper Thieving Tool")
 
-                # append into the '0' aperture
-                geo_elem = {}
-                geo_elem['solid'] = app_obj.new_solid_geometry
-                geo_elem['follow'] = app_obj.new_solid_geometry.exterior
-                app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
+    def __init__(self, layout, app):
+        self.app = app
+        self.decimals = self.app.decimals
+        self.units = self.app.defaults['units']
+        self.layout = layout
 
-            app_obj.grb_object.solid_geometry = MultiPolygon(geo_list).buffer(0.0000001).buffer(-0.0000001)
+        # ## 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(""))
 
-            app_obj.app.proc_container.update_view_text(' %s' % _("Append source file"))
-            # update the source file with the new geometry:
-            app_obj.grb_object.source_file = app_obj.app.export_gerber(obj_name=app_obj.grb_object.options['name'],
-                                                                       filename=None,
-                                                                       local_use=app_obj.grb_object,
-                                                                       use_thread=False)
-            app_obj.app.proc_container.update_view_text(' %s' % '')
-            app_obj.on_exit()
-            app_obj.app.inform.emit('[success] %s' % _("Copper Thieving Tool done."))
+        # ## Grid Layout
+        i_grid_lay = QtWidgets.QGridLayout()
+        self.layout.addLayout(i_grid_lay)
+        i_grid_lay.setColumnStretch(0, 0)
+        i_grid_lay.setColumnStretch(1, 1)
 
-        if run_threaded:
-            self.app.worker_task.emit({'fcn': job_thread_thieving, 'params': [self]})
-        else:
-            job_thread_thieving(self)
+        self.grb_object_combo = FCComboBox()
+        self.grb_object_combo.setModel(self.app.collection)
+        self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.grb_object_combo.is_last = True
+        self.grb_object_combo.obj_type = 'Gerber'
 
-    def on_add_ppm(self):
-        run_threaded = True
+        self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
+        self.grbobj_label.setToolTip(
+            _("Gerber Object to which will be added a copper thieving.")
+        )
 
-        if run_threaded:
-            proc = self.app.proc_container.new('%s ...' % _("P-Plating Mask"))
-        else:
-            QtWidgets.QApplication.processEvents()
+        i_grid_lay.addWidget(self.grbobj_label, 0, 0)
+        i_grid_lay.addWidget(self.grb_object_combo, 1, 0, 1, 2)
 
-        self.app.proc_container.view.set_busy('%s ...' % _("P-Plating Mask"))
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        i_grid_lay.addWidget(separator_line, 2, 0, 1, 2)
 
-        if run_threaded:
-            self.app.worker_task.emit({'fcn': self.on_new_pattern_plating_object, 'params': []})
-        else:
-            self.on_new_pattern_plating_object()
+        # ## Grid Layout
+        grid_lay = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay)
+        grid_lay.setColumnStretch(0, 0)
+        grid_lay.setColumnStretch(1, 1)
 
-    def on_new_pattern_plating_object(self):
-        # get the Gerber object on which the Copper thieving will be inserted
-        selection_index = self.sm_object_combo.currentIndex()
-        model_index = self.app.collection.index(selection_index, 0, self.sm_object_combo.rootModelIndex())
+        self.copper_fill_label = QtWidgets.QLabel('<b>%s</b>' % _('Parameters'))
+        self.copper_fill_label.setToolTip(
+            _("Parameters used for this tool.")
+        )
+        grid_lay.addWidget(self.copper_fill_label, 0, 0, 1, 2)
 
-        try:
-            self.sm_object = model_index.internalPointer().obj
-        except Exception as e:
-            log.debug("ToolCopperThieving.on_add_ppm() --> %s" % str(e))
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
-            return 'fail'
+        # CLEARANCE #
+        self.clearance_label = QtWidgets.QLabel('%s:' % _("Clearance"))
+        self.clearance_label.setToolTip(
+            _("This set the distance between the copper thieving components\n"
+              "(the polygon fill may be split in multiple polygons)\n"
+              "and the copper traces in the Gerber file.")
+        )
+        self.clearance_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.clearance_entry.set_range(0.00001, 9999.9999)
+        self.clearance_entry.set_precision(self.decimals)
+        self.clearance_entry.setSingleStep(0.1)
 
-        ppm_clearance = self.clearance_ppm_entry.get_value()
-        rb_thickness = self.rb_thickness
+        grid_lay.addWidget(self.clearance_label, 1, 0)
+        grid_lay.addWidget(self.clearance_entry, 1, 1)
 
-        self.app.proc_container.update_view_text(' %s' % _("Append PP-M geometry"))
-        geo_list = self.sm_object.solid_geometry
-        if isinstance(self.sm_object.solid_geometry, MultiPolygon):
-            geo_list = list(self.sm_object.solid_geometry.geoms)
+        # MARGIN #
+        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
+        self.margin_label.setToolTip(
+            _("Bounding box margin.")
+        )
+        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.margin_entry.set_range(0.0, 9999.9999)
+        self.margin_entry.set_precision(self.decimals)
+        self.margin_entry.setSingleStep(0.1)
 
-        # if the clearance is negative apply it to the original soldermask too
-        if ppm_clearance < 0:
-            temp_geo_list = []
-            for geo in geo_list:
-                temp_geo_list.append(geo.buffer(ppm_clearance))
-            geo_list = temp_geo_list
+        grid_lay.addWidget(self.margin_label, 2, 0)
+        grid_lay.addWidget(self.margin_entry, 2, 1)
 
-        plated_area = 0.0
-        for geo in geo_list:
-            plated_area += geo.area
+        # Reference #
+        self.reference_radio = RadioSet([
+            {'label': _('Itself'), 'value': 'itself'},
+            {"label": _("Area Selection"), "value": "area"},
+            {'label': _("Reference Object"), 'value': 'box'}
+        ], orientation='vertical', stretch=False)
+        self.reference_label = QtWidgets.QLabel(_("Reference:"))
+        self.reference_label.setToolTip(
+            _("- 'Itself' - the copper thieving extent is based on the object extent.\n"
+              "- 'Area Selection' - left mouse click to start selection of the area to be filled.\n"
+              "- 'Reference Object' - will do copper thieving within the area specified by another object.")
+        )
+        grid_lay.addWidget(self.reference_label, 3, 0)
+        grid_lay.addWidget(self.reference_radio, 3, 1)
 
-        if self.new_solid_geometry:
-            for geo in self.new_solid_geometry:
-                plated_area += geo.area
-        if self.robber_geo:
-            plated_area += self.robber_geo.area
-        self.plated_area_entry.set_value(plated_area)
+        self.ref_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
+        self.ref_combo_type_label.setToolTip(
+            _("The type of FlatCAM object to be used as copper thieving reference.\n"
+              "It can be Gerber, Excellon or Geometry.")
+        )
+        self.ref_combo_type = FCComboBox()
+        self.ref_combo_type.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
 
-        thieving_solid_geo = self.new_solid_geometry
-        robber_solid_geo = self.robber_geo
-        robber_line = self.robber_line
+        grid_lay.addWidget(self.ref_combo_type_label, 4, 0)
+        grid_lay.addWidget(self.ref_combo_type, 4, 1)
 
-        def obj_init(grb_obj, app_obj):
-            grb_obj.multitool = False
-            grb_obj.source_file = []
-            grb_obj.multigeo = False
-            grb_obj.follow = False
-            grb_obj.apertures = {}
-            grb_obj.solid_geometry = []
+        self.ref_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
+        self.ref_combo_label.setToolTip(
+            _("The FlatCAM object to be used as non copper clearing reference.")
+        )
+        self.ref_combo = FCComboBox()
+        self.ref_combo.setModel(self.app.collection)
+        self.ref_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.ref_combo.is_last = True
+        self.ref_combo.obj_type = {
+            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
+        }[self.ref_combo_type.get_value()]
 
-            # try:
-            #     grb_obj.options['xmin'] = 0
-            #     grb_obj.options['ymin'] = 0
-            #     grb_obj.options['xmax'] = 0
-            #     grb_obj.options['ymax'] = 0
-            # except KeyError:
-            #     pass
+        grid_lay.addWidget(self.ref_combo_label, 5, 0)
+        grid_lay.addWidget(self.ref_combo, 5, 1)
 
-            # if we have copper thieving geometry, add it
-            if thieving_solid_geo:
-                if '0' not in grb_obj.apertures:
-                    grb_obj.apertures['0'] = {}
-                    grb_obj.apertures['0']['geometry'] = []
-                    grb_obj.apertures['0']['type'] = 'REG'
-                    grb_obj.apertures['0']['size'] = 0.0
+        self.ref_combo.hide()
+        self.ref_combo_label.hide()
+        self.ref_combo_type.hide()
+        self.ref_combo_type_label.hide()
 
-                try:
-                    for poly in thieving_solid_geo:
-                        poly_b = poly.buffer(ppm_clearance)
+        # Bounding Box Type #
+        self.bbox_type_radio = RadioSet([
+            {'label': _('Rectangular'), 'value': 'rect'},
+            {"label": _("Minimal"), "value": "min"}
+        ], stretch=False)
+        self.bbox_type_label = QtWidgets.QLabel(_("Box Type:"))
+        self.bbox_type_label.setToolTip(
+            _("- 'Rectangular' - the bounding box will be of rectangular shape.\n"
+              "- 'Minimal' - the bounding box will be the convex hull shape.")
+        )
+        grid_lay.addWidget(self.bbox_type_label, 6, 0)
+        grid_lay.addWidget(self.bbox_type_radio, 6, 1)
+        self.bbox_type_label.hide()
+        self.bbox_type_radio.hide()
 
-                        # append to the new solid geometry
-                        geo_list.append(poly_b)
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line, 7, 0, 1, 2)
 
-                        # append into the '0' aperture
-                        geo_elem = {}
-                        geo_elem['solid'] = poly_b
-                        geo_elem['follow'] = poly_b.exterior
-                        grb_obj.apertures['0']['geometry'].append(deepcopy(geo_elem))
-                except TypeError:
-                    # append to the new solid geometry
-                    geo_list.append(thieving_solid_geo.buffer(ppm_clearance))
+        # Fill Type
+        self.fill_type_radio = RadioSet([
+            {'label': _('Solid'), 'value': 'solid'},
+            {"label": _("Dots Grid"), "value": "dot"},
+            {"label": _("Squares Grid"), "value": "square"},
+            {"label": _("Lines Grid"), "value": "line"}
+        ], orientation='vertical', stretch=False)
+        self.fill_type_label = QtWidgets.QLabel(_("Fill Type:"))
+        self.fill_type_label.setToolTip(
+            _("- 'Solid' - copper thieving will be a solid polygon.\n"
+              "- 'Dots Grid' - the empty area will be filled with a pattern of dots.\n"
+              "- 'Squares Grid' - the empty area will be filled with a pattern of squares.\n"
+              "- 'Lines Grid' - the empty area will be filled with a pattern of lines.")
+        )
+        grid_lay.addWidget(self.fill_type_label, 8, 0)
+        grid_lay.addWidget(self.fill_type_radio, 8, 1)
 
-                    # append into the '0' aperture
-                    geo_elem = {}
-                    geo_elem['solid'] = thieving_solid_geo.buffer(ppm_clearance)
-                    geo_elem['follow'] = thieving_solid_geo.buffer(ppm_clearance).exterior
-                    grb_obj.apertures['0']['geometry'].append(deepcopy(geo_elem))
+        # DOTS FRAME
+        self.dots_frame = QtWidgets.QFrame()
+        self.dots_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.dots_frame)
+        dots_grid = QtWidgets.QGridLayout()
+        dots_grid.setColumnStretch(0, 0)
+        dots_grid.setColumnStretch(1, 1)
+        dots_grid.setContentsMargins(0, 0, 0, 0)
+        self.dots_frame.setLayout(dots_grid)
+        self.dots_frame.hide()
 
-            # if we have robber bar geometry, add it
-            if robber_solid_geo:
-                aperture_found = None
-                for ap_id, ap_val in grb_obj.apertures.items():
-                    if ap_val['type'] == 'C' and ap_val['size'] == app_obj.rb_thickness + ppm_clearance:
-                        aperture_found = ap_id
-                        break
+        self.dots_label = QtWidgets.QLabel('<b>%s</b>:' % _("Dots Grid Parameters"))
+        dots_grid.addWidget(self.dots_label, 0, 0, 1, 2)
+
+        # Dot diameter #
+        self.dotdia_label = QtWidgets.QLabel('%s:' % _("Dia"))
+        self.dotdia_label.setToolTip(
+            _("Dot diameter in Dots Grid.")
+        )
+        self.dot_dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.dot_dia_entry.set_range(0.0, 9999.9999)
+        self.dot_dia_entry.set_precision(self.decimals)
+        self.dot_dia_entry.setSingleStep(0.1)
+
+        dots_grid.addWidget(self.dotdia_label, 1, 0)
+        dots_grid.addWidget(self.dot_dia_entry, 1, 1)
+
+        # Dot spacing #
+        self.dotspacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
+        self.dotspacing_label.setToolTip(
+            _("Distance between each two dots in Dots Grid.")
+        )
+        self.dot_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.dot_spacing_entry.set_range(0.0, 9999.9999)
+        self.dot_spacing_entry.set_precision(self.decimals)
+        self.dot_spacing_entry.setSingleStep(0.1)
+
+        dots_grid.addWidget(self.dotspacing_label, 2, 0)
+        dots_grid.addWidget(self.dot_spacing_entry, 2, 1)
+
+        # SQUARES FRAME
+        self.squares_frame = QtWidgets.QFrame()
+        self.squares_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.squares_frame)
+        squares_grid = QtWidgets.QGridLayout()
+        squares_grid.setColumnStretch(0, 0)
+        squares_grid.setColumnStretch(1, 1)
+        squares_grid.setContentsMargins(0, 0, 0, 0)
+        self.squares_frame.setLayout(squares_grid)
+        self.squares_frame.hide()
+
+        self.squares_label = QtWidgets.QLabel('<b>%s</b>:' % _("Squares Grid Parameters"))
+        squares_grid.addWidget(self.squares_label, 0, 0, 1, 2)
+
+        # Square Size #
+        self.square_size_label = QtWidgets.QLabel('%s:' % _("Size"))
+        self.square_size_label.setToolTip(
+            _("Square side size in Squares Grid.")
+        )
+        self.square_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.square_size_entry.set_range(0.0, 9999.9999)
+        self.square_size_entry.set_precision(self.decimals)
+        self.square_size_entry.setSingleStep(0.1)
+
+        squares_grid.addWidget(self.square_size_label, 1, 0)
+        squares_grid.addWidget(self.square_size_entry, 1, 1)
+
+        # Squares spacing #
+        self.squares_spacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
+        self.squares_spacing_label.setToolTip(
+            _("Distance between each two squares in Squares Grid.")
+        )
+        self.squares_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.squares_spacing_entry.set_range(0.0, 9999.9999)
+        self.squares_spacing_entry.set_precision(self.decimals)
+        self.squares_spacing_entry.setSingleStep(0.1)
+
+        squares_grid.addWidget(self.squares_spacing_label, 2, 0)
+        squares_grid.addWidget(self.squares_spacing_entry, 2, 1)
+
+        # LINES FRAME
+        self.lines_frame = QtWidgets.QFrame()
+        self.lines_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.lines_frame)
+        lines_grid = QtWidgets.QGridLayout()
+        lines_grid.setColumnStretch(0, 0)
+        lines_grid.setColumnStretch(1, 1)
+        lines_grid.setContentsMargins(0, 0, 0, 0)
+        self.lines_frame.setLayout(lines_grid)
+        self.lines_frame.hide()
+
+        self.lines_label = QtWidgets.QLabel('<b>%s</b>:' % _("Lines Grid Parameters"))
+        lines_grid.addWidget(self.lines_label, 0, 0, 1, 2)
+
+        # Square Size #
+        self.line_size_label = QtWidgets.QLabel('%s:' % _("Size"))
+        self.line_size_label.setToolTip(
+            _("Line thickness size in Lines Grid.")
+        )
+        self.line_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.line_size_entry.set_range(0.0, 9999.9999)
+        self.line_size_entry.set_precision(self.decimals)
+        self.line_size_entry.setSingleStep(0.1)
 
-                if aperture_found:
-                    geo_elem = {}
-                    geo_elem['solid'] = robber_solid_geo
-                    geo_elem['follow'] = robber_line
-                    grb_obj.apertures[aperture_found]['geometry'].append(deepcopy(geo_elem))
-                else:
-                    ap_keys = list(grb_obj.apertures.keys())
-                    max_apid = int(max(ap_keys))
-                    if ap_keys and max_apid != 0:
-                        new_apid = str(max_apid + 1)
-                    else:
-                        new_apid = '10'
+        lines_grid.addWidget(self.line_size_label, 1, 0)
+        lines_grid.addWidget(self.line_size_entry, 1, 1)
 
-                    grb_obj.apertures[new_apid] = {}
-                    grb_obj.apertures[new_apid]['type'] = 'C'
-                    grb_obj.apertures[new_apid]['size'] = rb_thickness + ppm_clearance
-                    grb_obj.apertures[new_apid]['geometry'] = []
+        # Lines spacing #
+        self.lines_spacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
+        self.lines_spacing_label.setToolTip(
+            _("Distance between each two lines in Lines Grid.")
+        )
+        self.lines_spacing_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.lines_spacing_entry.set_range(0.0, 9999.9999)
+        self.lines_spacing_entry.set_precision(self.decimals)
+        self.lines_spacing_entry.setSingleStep(0.1)
 
-                    geo_elem = {}
-                    geo_elem['solid'] = robber_solid_geo.buffer(ppm_clearance)
-                    geo_elem['follow'] = Polygon(robber_line).buffer(ppm_clearance / 2.0).exterior
-                    grb_obj.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
+        lines_grid.addWidget(self.lines_spacing_label, 2, 0)
+        lines_grid.addWidget(self.lines_spacing_entry, 2, 1)
 
-                geo_list.append(robber_solid_geo.buffer(ppm_clearance))
+        # ## Insert Copper Thieving
+        self.fill_button = QtWidgets.QPushButton(_("Insert Copper thieving"))
+        self.fill_button.setToolTip(
+            _("Will add a polygon (may be split in multiple parts)\n"
+              "that will surround the actual Gerber traces at a certain distance.")
+        )
+        self.fill_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        self.layout.addWidget(self.fill_button)
 
-            grb_obj.solid_geometry = MultiPolygon(geo_list).buffer(0.0000001).buffer(-0.0000001)
+        # ## Grid Layout
+        grid_lay_1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay_1)
+        grid_lay_1.setColumnStretch(0, 0)
+        grid_lay_1.setColumnStretch(1, 1)
+        grid_lay_1.setColumnStretch(2, 0)
 
-            app_obj.proc_container.update_view_text(' %s' % _("Append source file"))
-            # update the source file with the new geometry:
-            grb_obj.source_file = app_obj.export_gerber(obj_name=name,
-                                                        filename=None,
-                                                        local_use=grb_obj,
-                                                        use_thread=False)
-            app_obj.proc_container.update_view_text(' %s' % '')
+        separator_line_1 = QtWidgets.QFrame()
+        separator_line_1.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line_1.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay_1.addWidget(separator_line_1, 0, 0, 1, 3)
 
-        # Object name
-        obj_name, separatpr, obj_extension = self.sm_object.options['name'].rpartition('.')
-        name = '%s_%s.%s' % (obj_name, 'plating_mask', obj_extension)
+        grid_lay_1.addWidget(QtWidgets.QLabel(''))
 
-        self.app.app_obj.new_object('gerber', name, obj_init, autoselected=False)
+        self.robber_bar_label = QtWidgets.QLabel('<b>%s</b>' % _('Robber Bar Parameters'))
+        self.robber_bar_label.setToolTip(
+            _("Parameters used for the robber bar.\n"
+              "Robber bar = copper border to help in pattern hole plating.")
+        )
+        grid_lay_1.addWidget(self.robber_bar_label, 1, 0, 1, 3)
 
-        # Register recent file
-        self.app.file_opened.emit("gerber", name)
+        # ROBBER BAR MARGIN #
+        self.rb_margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
+        self.rb_margin_label.setToolTip(
+            _("Bounding box margin for robber bar.")
+        )
+        self.rb_margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.rb_margin_entry.set_range(-9999.9999, 9999.9999)
+        self.rb_margin_entry.set_precision(self.decimals)
+        self.rb_margin_entry.setSingleStep(0.1)
 
-        self.on_exit()
-        self.app.inform.emit('[success] %s' % _("Generating Pattern Plating Mask done."))
+        grid_lay_1.addWidget(self.rb_margin_label, 2, 0)
+        grid_lay_1.addWidget(self.rb_margin_entry, 2, 1, 1, 2)
 
-    def replot(self, obj):
-        def worker_task():
-            with self.app.proc_container.new('%s...' % _("Plotting")):
-                obj.plot()
+        # THICKNESS #
+        self.rb_thickness_label = QtWidgets.QLabel('%s:' % _("Thickness"))
+        self.rb_thickness_label.setToolTip(
+            _("The robber bar thickness.")
+        )
+        self.rb_thickness_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.rb_thickness_entry.set_range(0.0000, 9999.9999)
+        self.rb_thickness_entry.set_precision(self.decimals)
+        self.rb_thickness_entry.setSingleStep(0.1)
 
-        self.app.worker_task.emit({'fcn': worker_task, 'params': []})
+        grid_lay_1.addWidget(self.rb_thickness_label, 3, 0)
+        grid_lay_1.addWidget(self.rb_thickness_entry, 3, 1, 1, 2)
 
-    def on_exit(self):
-        # plot the objects
-        if self.grb_object:
-            self.replot(obj=self.grb_object)
+        # ## Insert Robber Bar
+        self.rb_button = QtWidgets.QPushButton(_("Insert Robber Bar"))
+        self.rb_button.setToolTip(
+            _("Will add a polygon with a defined thickness\n"
+              "that will surround the actual Gerber object\n"
+              "at a certain distance.\n"
+              "Required when doing holes pattern plating.")
+        )
+        self.rb_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        grid_lay_1.addWidget(self.rb_button, 4, 0, 1, 3)
 
-        if self.sm_object:
-            self.replot(obj=self.sm_object)
+        separator_line_2 = QtWidgets.QFrame()
+        separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay_1.addWidget(separator_line_2, 5, 0, 1, 3)
 
-        # update the bounding box values
-        try:
-            a, b, c, d = self.grb_object.bounds()
-            self.grb_object.options['xmin'] = a
-            self.grb_object.options['ymin'] = b
-            self.grb_object.options['xmax'] = c
-            self.grb_object.options['ymax'] = d
-        except Exception as e:
-            log.debug("ToolCopperThieving.on_exit() bounds -> copper thieving Gerber error --> %s" % str(e))
+        self.patern_mask_label = QtWidgets.QLabel('<b>%s</b>' % _('Pattern Plating Mask'))
+        self.patern_mask_label.setToolTip(
+            _("Generate a mask for pattern plating.")
+        )
+        grid_lay_1.addWidget(self.patern_mask_label, 6, 0, 1, 3)
 
-        # update the bounding box values
-        try:
-            a, b, c, d = self.sm_object.bounds()
-            self.sm_object.options['xmin'] = a
-            self.sm_object.options['ymin'] = b
-            self.sm_object.options['xmax'] = c
-            self.sm_object.options['ymax'] = d
-        except Exception as e:
-            log.debug("ToolCopperThieving.on_exit() bounds -> pattern plating mask error --> %s" % str(e))
+        self.sm_obj_label = QtWidgets.QLabel("%s:" % _("Select Soldermask object"))
+        self.sm_obj_label.setToolTip(
+            _("Gerber Object with the soldermask.\n"
+              "It will be used as a base for\n"
+              "the pattern plating mask.")
+        )
 
-        # reset the variables
-        self.grb_object = None
-        self.sm_object = None
-        self.ref_obj = None
-        self.sel_rect = []
+        self.sm_object_combo = FCComboBox()
+        self.sm_object_combo.setModel(self.app.collection)
+        self.sm_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.sm_object_combo.is_last = True
+        self.sm_object_combo.obj_type = 'Gerber'
 
-        # Events ID
-        self.mr = None
-        self.mm = None
+        grid_lay_1.addWidget(self.sm_obj_label, 7, 0, 1, 3)
+        grid_lay_1.addWidget(self.sm_object_combo, 8, 0, 1, 3)
 
-        # Mouse cursor positions
-        self.mouse_is_dragging = False
-        self.cursor_pos = (0, 0)
-        self.first_click = False
+        # Openings CLEARANCE #
+        self.clearance_ppm_label = QtWidgets.QLabel('%s:' % _("Clearance"))
+        self.clearance_ppm_label.setToolTip(
+            _("The distance between the possible copper thieving elements\n"
+              "and/or robber bar and the actual openings in the mask.")
+        )
+        self.clearance_ppm_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.clearance_ppm_entry.set_range(-9999.9999, 9999.9999)
+        self.clearance_ppm_entry.set_precision(self.decimals)
+        self.clearance_ppm_entry.setSingleStep(0.1)
 
-        # if True it means we exited from tool in the middle of area adding therefore disconnect the events
-        if self.area_method is True:
-            self.app.delete_selection_shape()
-            self.area_method = False
+        grid_lay_1.addWidget(self.clearance_ppm_label, 9, 0)
+        grid_lay_1.addWidget(self.clearance_ppm_entry, 9, 1, 1, 2)
 
-            if self.app.is_legacy is False:
-                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
-                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
-            else:
-                self.app.plotcanvas.graph_event_disconnect(self.mr)
-                self.app.plotcanvas.graph_event_disconnect(self.mm)
+        # Plated area
+        self.plated_area_label = QtWidgets.QLabel('%s:' % _("Plated area"))
+        self.plated_area_label.setToolTip(
+            _("The area to be plated by pattern plating.\n"
+              "Basically is made from the openings in the plating mask.\n\n"
+              "<<WARNING>> - the calculated area is actually a bit larger\n"
+              "due of the fact that the soldermask openings are by design\n"
+              "a bit larger than the copper pads, and this area is\n"
+              "calculated from the soldermask openings.")
+        )
+        self.plated_area_entry = FCEntry()
+        self.plated_area_entry.setDisabled(True)
 
-            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
-                                                                  self.app.on_mouse_click_over_plot)
-            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
-                                                                  self.app.on_mouse_move_over_plot)
-            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                                  self.app.on_mouse_click_release_over_plot)
+        if self.units.upper() == 'MM':
+            self.units_area_label = QtWidgets.QLabel('%s<sup>2</sup>' % _("mm"))
+        else:
+            self.units_area_label = QtWidgets.QLabel('%s<sup>2</sup>' % _("in"))
 
-        self.app.call_source = "app"
-        self.app.inform.emit('[success] %s' % _("Copper Thieving Tool exit."))
+        grid_lay_1.addWidget(self.plated_area_label, 10, 0)
+        grid_lay_1.addWidget(self.plated_area_entry, 10, 1)
+        grid_lay_1.addWidget(self.units_area_label, 10, 2)
 
-    def flatten(self, geometry):
-        """
-        Creates a list of non-iterable linear geometry objects.
-        :param geometry: Shapely type or list or list of list of such.
+        # ## Pattern Plating Mask
+        self.ppm_button = QtWidgets.QPushButton(_("Generate pattern plating mask"))
+        self.ppm_button.setToolTip(
+            _("Will add to the soldermask gerber geometry\n"
+              "the geometries of the copper thieving and/or\n"
+              "the robber bar if those were generated.")
+        )
+        self.ppm_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        grid_lay_1.addWidget(self.ppm_button, 11, 0, 1, 3)
 
-        Results are placed in self.flat_geometry
-        """
+        self.layout.addStretch()
 
-        # ## If iterable, expand recursively.
-        try:
-            for geo in geometry:
-                if geo is not None:
-                    self.flatten(geometry=geo)
+        # ## 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)
 
-        # ## Not iterable, do the actual indexing and add.
-        except TypeError:
-            self.flat_geometry.append(geometry)
+        # #################################### FINSIHED GUI ###########################
+        # #############################################################################
 
-        return self.flat_geometry
+    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)

+ 207 - 173
appTools/ToolCorners.py

@@ -28,8 +28,6 @@ log = logging.getLogger('base')
 
 class ToolCorners(AppTool):
 
-    toolName = _("Corner Markers Tool")
-
     def __init__(self, app):
         AppTool.__init__(self, app)
 
@@ -39,158 +37,11 @@ class ToolCorners(AppTool):
         self.decimals = self.app.decimals
         self.units = ''
 
-        # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(title_label)
-        self.layout.addWidget(QtWidgets.QLabel(''))
-
-        # Gerber object #
-        self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("GERBER"))
-        self.object_label.setToolTip(
-            _("The Gerber object to which will be added corner markers.")
-        )
-        self.object_combo = FCComboBox()
-        self.object_combo.setModel(self.app.collection)
-        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.object_combo.is_last = True
-        self.object_combo.obj_type = "Gerber"
-
-        self.layout.addWidget(self.object_label)
-        self.layout.addWidget(self.object_combo)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.layout.addWidget(separator_line)
-
-        self.points_label = QtWidgets.QLabel('<b>%s:</b>' % _('Locations'))
-        self.points_label.setToolTip(
-            _("Locations where to place corner markers.")
-        )
-        self.layout.addWidget(self.points_label)
-
-        # BOTTOM LEFT
-        self.bl_cb = FCCheckBox(_("Bottom Left"))
-        self.layout.addWidget(self.bl_cb)
-
-        # BOTTOM RIGHT
-        self.br_cb = FCCheckBox(_("Bottom Right"))
-        self.layout.addWidget(self.br_cb)
-
-        # TOP LEFT
-        self.tl_cb = FCCheckBox(_("Top Left"))
-        self.layout.addWidget(self.tl_cb)
-
-        # TOP RIGHT
-        self.tr_cb = FCCheckBox(_("Top Right"))
-        self.layout.addWidget(self.tr_cb)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.layout.addWidget(separator_line)
-
-        # Toggle ALL
-        self.toggle_all_cb = FCCheckBox(_("Toggle ALL"))
-        self.layout.addWidget(self.toggle_all_cb)
-
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.layout.addWidget(separator_line)
-
-        # ## Grid Layout
-        grid_lay = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_lay)
-        grid_lay.setColumnStretch(0, 0)
-        grid_lay.setColumnStretch(1, 1)
-
-        self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
-        self.param_label.setToolTip(
-            _("Parameters used for this tool.")
-        )
-        grid_lay.addWidget(self.param_label, 0, 0, 1, 2)
-
-        # Thickness #
-        self.thick_label = QtWidgets.QLabel('%s:' % _("Thickness"))
-        self.thick_label.setToolTip(
-            _("The thickness of the line that makes the corner marker.")
-        )
-        self.thick_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.thick_entry.set_range(0.0000, 9.9999)
-        self.thick_entry.set_precision(self.decimals)
-        self.thick_entry.setWrapping(True)
-        self.thick_entry.setSingleStep(10 ** -self.decimals)
-
-        grid_lay.addWidget(self.thick_label, 1, 0)
-        grid_lay.addWidget(self.thick_entry, 1, 1)
-
-        # Length #
-        self.l_label = QtWidgets.QLabel('%s:' % _("Length"))
-        self.l_label.setToolTip(
-            _("The length of the line that makes the corner marker.")
-        )
-        self.l_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.l_entry.set_range(-9999.9999, 9999.9999)
-        self.l_entry.set_precision(self.decimals)
-        self.l_entry.setSingleStep(10 ** -self.decimals)
-
-        grid_lay.addWidget(self.l_label, 2, 0)
-        grid_lay.addWidget(self.l_entry, 2, 1)
-
-        # Margin #
-        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
-        self.margin_label.setToolTip(
-            _("Bounding box margin.")
-        )
-        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.margin_entry.set_range(-9999.9999, 9999.9999)
-        self.margin_entry.set_precision(self.decimals)
-        self.margin_entry.setSingleStep(0.1)
-
-        grid_lay.addWidget(self.margin_label, 3, 0)
-        grid_lay.addWidget(self.margin_entry, 3, 1)
-
-        separator_line_2 = QtWidgets.QFrame()
-        separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line_2, 4, 0, 1, 2)
-
-        # ## Insert Corner Marker
-        self.add_marker_button = FCButton(_("Add Marker"))
-        self.add_marker_button.setToolTip(
-            _("Will add corner markers to the selected Gerber file.")
-        )
-        self.add_marker_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay.addWidget(self.add_marker_button, 11, 0, 1, 2)
-
-        self.layout.addStretch()
-
-        # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
-        self.reset_button.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)
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = CornersUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
 
         # Objects involved in Copper thieving
         self.grb_object = None
@@ -204,8 +55,8 @@ class ToolCorners(AppTool):
         self.grb_steps_per_circle = self.app.defaults["gerber_circle_steps"]
 
         # SIGNALS
-        self.add_marker_button.clicked.connect(self.add_markers)
-        self.toggle_all_cb.toggled.connect(self.on_toggle_all)
+        self.ui.add_marker_button.clicked.connect(self.add_markers)
+        self.ui.toggle_all_cb.toggled.connect(self.on_toggle_all)
 
     def run(self, toggle=True):
         self.app.defaults.report_usage("ToolCorners()")
@@ -240,27 +91,27 @@ class ToolCorners(AppTool):
 
     def set_tool_ui(self):
         self.units = self.app.defaults['units']
-        self.thick_entry.set_value(self.app.defaults["tools_corners_thickness"])
-        self.l_entry.set_value(float(self.app.defaults["tools_corners_length"]))
-        self.margin_entry.set_value(float(self.app.defaults["tools_corners_margin"]))
-        self.toggle_all_cb.set_value(False)
+        self.ui.thick_entry.set_value(self.app.defaults["tools_corners_thickness"])
+        self.ui.l_entry.set_value(float(self.app.defaults["tools_corners_length"]))
+        self.ui.margin_entry.set_value(float(self.app.defaults["tools_corners_margin"]))
+        self.ui.toggle_all_cb.set_value(False)
 
     def on_toggle_all(self, val):
-        self.bl_cb.set_value(val)
-        self.br_cb.set_value(val)
-        self.tl_cb.set_value(val)
-        self.tr_cb.set_value(val)
+        self.ui.bl_cb.set_value(val)
+        self.ui.br_cb.set_value(val)
+        self.ui.tl_cb.set_value(val)
+        self.ui.tr_cb.set_value(val)
 
     def add_markers(self):
         self.app.call_source = "corners_tool"
-        tl_state = self.tl_cb.get_value()
-        tr_state = self.tr_cb.get_value()
-        bl_state = self.bl_cb.get_value()
-        br_state = self.br_cb.get_value()
+        tl_state = self.ui.tl_cb.get_value()
+        tr_state = self.ui.tr_cb.get_value()
+        bl_state = self.ui.bl_cb.get_value()
+        br_state = self.ui.br_cb.get_value()
 
         # get the Gerber object on which the corner marker will be inserted
-        selection_index = self.object_combo.currentIndex()
-        model_index = self.app.collection.index(selection_index, 0, self.object_combo.rootModelIndex())
+        selection_index = self.ui.object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.ui.object_combo.rootModelIndex())
 
         try:
             self.grb_object = model_index.internalPointer().obj
@@ -296,9 +147,9 @@ class ToolCorners(AppTool):
         :return:                None
         """
 
-        line_thickness = self.thick_entry.get_value()
-        line_length = self.l_entry.get_value()
-        margin = self.margin_entry.get_value()
+        line_thickness = self.ui.thick_entry.get_value()
+        line_length = self.ui.l_entry.get_value()
+        margin = self.ui.margin_entry.get_value()
 
         geo_list = []
 
@@ -439,3 +290,186 @@ class ToolCorners(AppTool):
 
         self.app.call_source = "app"
         self.app.inform.emit('[success] %s' % _("Corners Tool exit."))
+
+
+class CornersUI:
+
+    toolName = _("Corner Markers Tool")
+
+    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;
+                                }
+                                """)
+        self.layout.addWidget(title_label)
+        self.layout.addWidget(QtWidgets.QLabel(""))
+
+        # Gerber object #
+        self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("GERBER"))
+        self.object_label.setToolTip(
+            _("The Gerber object to which will be added corner markers.")
+        )
+        self.object_combo = FCComboBox()
+        self.object_combo.setModel(self.app.collection)
+        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.object_combo.is_last = True
+        self.object_combo.obj_type = "Gerber"
+
+        self.layout.addWidget(self.object_label)
+        self.layout.addWidget(self.object_combo)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.layout.addWidget(separator_line)
+
+        self.points_label = QtWidgets.QLabel('<b>%s:</b>' % _('Locations'))
+        self.points_label.setToolTip(
+            _("Locations where to place corner markers.")
+        )
+        self.layout.addWidget(self.points_label)
+
+        # BOTTOM LEFT
+        self.bl_cb = FCCheckBox(_("Bottom Left"))
+        self.layout.addWidget(self.bl_cb)
+
+        # BOTTOM RIGHT
+        self.br_cb = FCCheckBox(_("Bottom Right"))
+        self.layout.addWidget(self.br_cb)
+
+        # TOP LEFT
+        self.tl_cb = FCCheckBox(_("Top Left"))
+        self.layout.addWidget(self.tl_cb)
+
+        # TOP RIGHT
+        self.tr_cb = FCCheckBox(_("Top Right"))
+        self.layout.addWidget(self.tr_cb)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.layout.addWidget(separator_line)
+
+        # Toggle ALL
+        self.toggle_all_cb = FCCheckBox(_("Toggle ALL"))
+        self.layout.addWidget(self.toggle_all_cb)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.layout.addWidget(separator_line)
+
+        # ## Grid Layout
+        grid_lay = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay)
+        grid_lay.setColumnStretch(0, 0)
+        grid_lay.setColumnStretch(1, 1)
+
+        self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
+        self.param_label.setToolTip(
+            _("Parameters used for this tool.")
+        )
+        grid_lay.addWidget(self.param_label, 0, 0, 1, 2)
+
+        # Thickness #
+        self.thick_label = QtWidgets.QLabel('%s:' % _("Thickness"))
+        self.thick_label.setToolTip(
+            _("The thickness of the line that makes the corner marker.")
+        )
+        self.thick_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.thick_entry.set_range(0.0000, 9.9999)
+        self.thick_entry.set_precision(self.decimals)
+        self.thick_entry.setWrapping(True)
+        self.thick_entry.setSingleStep(10 ** -self.decimals)
+
+        grid_lay.addWidget(self.thick_label, 1, 0)
+        grid_lay.addWidget(self.thick_entry, 1, 1)
+
+        # Length #
+        self.l_label = QtWidgets.QLabel('%s:' % _("Length"))
+        self.l_label.setToolTip(
+            _("The length of the line that makes the corner marker.")
+        )
+        self.l_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.l_entry.set_range(-9999.9999, 9999.9999)
+        self.l_entry.set_precision(self.decimals)
+        self.l_entry.setSingleStep(10 ** -self.decimals)
+
+        grid_lay.addWidget(self.l_label, 2, 0)
+        grid_lay.addWidget(self.l_entry, 2, 1)
+
+        # Margin #
+        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
+        self.margin_label.setToolTip(
+            _("Bounding box margin.")
+        )
+        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.margin_entry.set_range(-9999.9999, 9999.9999)
+        self.margin_entry.set_precision(self.decimals)
+        self.margin_entry.setSingleStep(0.1)
+
+        grid_lay.addWidget(self.margin_label, 3, 0)
+        grid_lay.addWidget(self.margin_entry, 3, 1)
+
+        separator_line_2 = QtWidgets.QFrame()
+        separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line_2, 4, 0, 1, 2)
+
+        # ## Insert Corner Marker
+        self.add_marker_button = FCButton(_("Add Marker"))
+        self.add_marker_button.setToolTip(
+            _("Will add corner markers to the selected Gerber file.")
+        )
+        self.add_marker_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        grid_lay.addWidget(self.add_marker_button, 11, 0, 1, 2)
+
+        self.layout.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.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)
+
+        # #################################### 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)

+ 0 - 1
appTools/ToolDblSided.py

@@ -509,7 +509,6 @@ class DsidedUI:
                                 }
                                 """)
         self.layout.addWidget(title_label)
-
         self.layout.addWidget(QtWidgets.QLabel(""))
 
         # ## Grid Layout

+ 448 - 412
appTools/ToolFiducials.py

@@ -30,8 +30,6 @@ log = logging.getLogger('base')
 
 class ToolFiducials(AppTool):
 
-    toolName = _("Fiducials Tool")
-
     def __init__(self, app):
         AppTool.__init__(self, app)
 
@@ -41,435 +39,153 @@ class ToolFiducials(AppTool):
         self.decimals = self.app.decimals
         self.units = ''
 
-        # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(title_label)
-        self.layout.addWidget(QtWidgets.QLabel(''))
-
-        self.points_label = QtWidgets.QLabel('<b>%s:</b>' % _('Fiducials Coordinates'))
-        self.points_label.setToolTip(
-            _("A table with the fiducial points coordinates,\n"
-              "in the format (x, y).")
-        )
-        self.layout.addWidget(self.points_label)
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = FidoUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
 
-        self.points_table = FCTable()
-        self.points_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+        # Objects involved in Copper thieving
+        self.grb_object = None
+        self.sm_object = None
 
-        self.points_table.setColumnCount(3)
-        self.points_table.setHorizontalHeaderLabels(
-            [
-                '#',
-                _("Name"),
-                _("Coordinates"),
-            ]
-        )
-        self.points_table.setRowCount(3)
-        row = 0
-        flags = QtCore.Qt.ItemIsEnabled
+        self.copper_obj_set = set()
+        self.sm_obj_set = set()
 
-        # BOTTOM LEFT
-        id_item_1 = QtWidgets.QTableWidgetItem('%d' % 1)
-        id_item_1.setFlags(flags)
-        self.points_table.setItem(row, 0, id_item_1)  # Tool name/id
+        # store the flattened geometry here:
+        self.flat_geometry = []
 
-        self.bottom_left_coords_lbl = QtWidgets.QTableWidgetItem('%s' % _('Bottom Left'))
-        self.bottom_left_coords_lbl.setFlags(flags)
-        self.points_table.setItem(row, 1, self.bottom_left_coords_lbl)
-        self.bottom_left_coords_entry = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.bottom_left_coords_entry)
-        row += 1
+        # Events ID
+        self.mr = None
+        self.mm = None
 
-        # TOP RIGHT
-        id_item_2 = QtWidgets.QTableWidgetItem('%d' % 2)
-        id_item_2.setFlags(flags)
-        self.points_table.setItem(row, 0, id_item_2)  # Tool name/id
+        # Mouse cursor positions
+        self.cursor_pos = (0, 0)
+        self.first_click = False
 
-        self.top_right_coords_lbl = QtWidgets.QTableWidgetItem('%s' % _('Top Right'))
-        self.top_right_coords_lbl.setFlags(flags)
-        self.points_table.setItem(row, 1, self.top_right_coords_lbl)
-        self.top_right_coords_entry = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.top_right_coords_entry)
-        row += 1
+        self.mode_method = False
 
-        # Second Point
-        self.id_item_3 = QtWidgets.QTableWidgetItem('%d' % 3)
-        self.id_item_3.setFlags(flags)
-        self.points_table.setItem(row, 0, self.id_item_3)  # Tool name/id
+        # Tool properties
+        self.fid_dia = None
+        self.sm_opening_dia = None
 
-        self.sec_point_coords_lbl = QtWidgets.QTableWidgetItem('%s' % _('Second Point'))
-        self.sec_point_coords_lbl.setFlags(flags)
-        self.points_table.setItem(row, 1, self.sec_point_coords_lbl)
-        self.sec_points_coords_entry = EvalEntry()
-        self.points_table.setCellWidget(row, 2, self.sec_points_coords_entry)
+        self.margin_val = None
+        self.sec_position = None
 
-        vertical_header = self.points_table.verticalHeader()
-        vertical_header.hide()
-        self.points_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+        self.grb_steps_per_circle = self.app.defaults["gerber_circle_steps"]
 
-        horizontal_header = self.points_table.horizontalHeader()
-        horizontal_header.setMinimumSectionSize(10)
-        horizontal_header.setDefaultSectionSize(70)
+        self.click_points = []
 
-        self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
-        # for x in range(4):
-        #     self.points_table.resizeColumnToContents(x)
-        self.points_table.resizeColumnsToContents()
-        self.points_table.resizeRowsToContents()
+        # SIGNALS
+        self.ui.add_cfid_button.clicked.connect(self.add_fiducials)
+        self.ui.add_sm_opening_button.clicked.connect(self.add_soldermask_opening)
 
-        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.resizeSection(0, 20)
-        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
-        horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
+        self.ui.fid_type_radio.activated_custom.connect(self.on_fiducial_type)
+        self.ui.pos_radio.activated_custom.connect(self.on_second_point)
+        self.ui.mode_radio.activated_custom.connect(self.on_method_change)
+        self.ui.reset_button.clicked.connect(self.set_tool_ui)
 
-        self.points_table.setMinimumHeight(self.points_table.getHeight() + 2)
-        self.points_table.setMaximumHeight(self.points_table.getHeight() + 2)
+    def run(self, toggle=True):
+        self.app.defaults.report_usage("ToolFiducials()")
 
-        # remove the frame on the QLineEdit childrens of the table
-        for row in range(self.points_table.rowCount()):
-            self.points_table.cellWidget(row, 2).setFrame(False)
+        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])
 
-        self.layout.addWidget(self.points_table)
+        AppTool.run(self)
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.layout.addWidget(separator_line)
+        self.set_tool_ui()
 
-        # ## Grid Layout
-        grid_lay = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_lay)
-        grid_lay.setColumnStretch(0, 0)
-        grid_lay.setColumnStretch(1, 1)
+        self.app.ui.notebook.setTabText(2, _("Fiducials Tool"))
 
-        self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
-        self.param_label.setToolTip(
-            _("Parameters used for this tool.")
-        )
-        grid_lay.addWidget(self.param_label, 0, 0, 1, 2)
+    def install(self, icon=None, separator=None, **kwargs):
+        AppTool.install(self, icon, separator, shortcut='Alt+F', **kwargs)
 
-        # DIAMETER #
-        self.size_label = QtWidgets.QLabel('%s:' % _("Size"))
-        self.size_label.setToolTip(
-            _("This set the fiducial diameter if fiducial type is circular,\n"
-              "otherwise is the size of the fiducial.\n"
-              "The soldermask opening is double than that.")
-        )
-        self.fid_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.fid_size_entry.set_range(1.0000, 3.0000)
-        self.fid_size_entry.set_precision(self.decimals)
-        self.fid_size_entry.setWrapping(True)
-        self.fid_size_entry.setSingleStep(0.1)
+    def set_tool_ui(self):
+        self.units = self.app.defaults['units']
 
-        grid_lay.addWidget(self.size_label, 1, 0)
-        grid_lay.addWidget(self.fid_size_entry, 1, 1)
+        self.ui.fid_size_entry.set_value(self.app.defaults["tools_fiducials_dia"])
+        self.ui.margin_entry.set_value(float(self.app.defaults["tools_fiducials_margin"]))
+        self.ui.mode_radio.set_value(self.app.defaults["tools_fiducials_mode"])
+        self.ui.pos_radio.set_value(self.app.defaults["tools_fiducials_second_pos"])
+        self.ui.fid_type_radio.set_value(self.app.defaults["tools_fiducials_type"])
+        self.ui.line_thickness_entry.set_value(float(self.app.defaults["tools_fiducials_line_thickness"]))
 
-        # MARGIN #
-        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
-        self.margin_label.setToolTip(
-            _("Bounding box margin.")
-        )
-        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.margin_entry.set_range(-9999.9999, 9999.9999)
-        self.margin_entry.set_precision(self.decimals)
-        self.margin_entry.setSingleStep(0.1)
+        self.click_points = []
+        self.ui.bottom_left_coords_entry.set_value('')
+        self.ui.top_right_coords_entry.set_value('')
+        self.ui.sec_points_coords_entry.set_value('')
 
-        grid_lay.addWidget(self.margin_label, 2, 0)
-        grid_lay.addWidget(self.margin_entry, 2, 1)
+        self.copper_obj_set = set()
+        self.sm_obj_set = set()
 
-        # Mode #
-        self.mode_radio = RadioSet([
-            {'label': _('Auto'), 'value': 'auto'},
-            {"label": _("Manual"), "value": "manual"}
-        ], stretch=False)
-        self.mode_label = QtWidgets.QLabel(_("Mode:"))
-        self.mode_label.setToolTip(
-            _("- 'Auto' - automatic placement of fiducials in the corners of the bounding box.\n "
-              "- 'Manual' - manual placement of fiducials.")
-        )
-        grid_lay.addWidget(self.mode_label, 3, 0)
-        grid_lay.addWidget(self.mode_radio, 3, 1)
+    def on_second_point(self, val):
+        if val == 'no':
+            self.ui.id_item_3.setFlags(QtCore.Qt.NoItemFlags)
+            self.ui.sec_point_coords_lbl.setFlags(QtCore.Qt.NoItemFlags)
+            self.ui.sec_points_coords_entry.setDisabled(True)
+        else:
+            self.ui.id_item_3.setFlags(QtCore.Qt.ItemIsEnabled)
+            self.ui.sec_point_coords_lbl.setFlags(QtCore.Qt.ItemIsEnabled)
+            self.ui.sec_points_coords_entry.setDisabled(False)
 
-        # Position for second fiducial #
-        self.pos_radio = RadioSet([
-            {'label': _('Up'), 'value': 'up'},
-            {"label": _("Down"), "value": "down"},
-            {"label": _("None"), "value": "no"}
-        ], stretch=False)
-        self.pos_label = QtWidgets.QLabel('%s:' % _("Second fiducial"))
-        self.pos_label.setToolTip(
-            _("The position for the second fiducial.\n"
-              "- 'Up' - the order is: bottom-left, top-left, top-right.\n"
-              "- 'Down' - the order is: bottom-left, bottom-right, top-right.\n"
-              "- 'None' - there is no second fiducial. The order is: bottom-left, top-right.")
-        )
-        grid_lay.addWidget(self.pos_label, 4, 0)
-        grid_lay.addWidget(self.pos_radio, 4, 1)
+    def on_method_change(self, val):
+        """
+        Make sure that on method change we disconnect the event handlers and reset the points storage
+        :param val: value of the Radio button which trigger this method
+        :return: None
+        """
+        if val == 'auto':
+            self.click_points = []
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line, 5, 0, 1, 2)
+            try:
+                self.disconnect_event_handlers()
+            except TypeError:
+                pass
 
-        # Fiducial type #
-        self.fid_type_radio = RadioSet([
-            {'label': _('Circular'), 'value': 'circular'},
-            {"label": _("Cross"), "value": "cross"},
-            {"label": _("Chess"), "value": "chess"}
-        ], stretch=False)
-        self.fid_type_label = QtWidgets.QLabel('%s:' % _("Fiducial Type"))
-        self.fid_type_label.setToolTip(
-            _("The type of fiducial.\n"
-              "- 'Circular' - this is the regular fiducial.\n"
-              "- 'Cross' - cross lines fiducial.\n"
-              "- 'Chess' - chess pattern fiducial.")
-        )
-        grid_lay.addWidget(self.fid_type_label, 6, 0)
-        grid_lay.addWidget(self.fid_type_radio, 6, 1)
+    def on_fiducial_type(self, val):
+        if val == 'cross':
+            self.ui.line_thickness_label.setDisabled(False)
+            self.ui.line_thickness_entry.setDisabled(False)
+        else:
+            self.ui.line_thickness_label.setDisabled(True)
+            self.ui.line_thickness_entry.setDisabled(True)
 
-        # Line Thickness #
-        self.line_thickness_label = QtWidgets.QLabel('%s:' % _("Line thickness"))
-        self.line_thickness_label.setToolTip(
-            _("Thickness of the line that makes the fiducial.")
-        )
-        self.line_thickness_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.line_thickness_entry.set_range(0.00001, 9999.9999)
-        self.line_thickness_entry.set_precision(self.decimals)
-        self.line_thickness_entry.setSingleStep(0.1)
+    def add_fiducials(self):
+        self.app.call_source = "fiducials_tool"
 
-        grid_lay.addWidget(self.line_thickness_label, 7, 0)
-        grid_lay.addWidget(self.line_thickness_entry, 7, 1)
+        self.mode_method = self.ui.mode_radio.get_value()
+        self.margin_val = self.ui.margin_entry.get_value()
+        self.sec_position = self.ui.pos_radio.get_value()
+        fid_type = self.ui.fid_type_radio.get_value()
 
-        separator_line_1 = QtWidgets.QFrame()
-        separator_line_1.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line_1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line_1, 8, 0, 1, 2)
+        self.click_points = []
 
-        # Copper Gerber object
-        self.grb_object_combo = FCComboBox()
-        self.grb_object_combo.setModel(self.app.collection)
-        self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.grb_object_combo.is_last = True
-        self.grb_object_combo.obj_type = "Gerber"
-
-        self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
-        self.grbobj_label.setToolTip(
-            _("Gerber Object to which will be added a copper thieving.")
-        )
-
-        grid_lay.addWidget(self.grbobj_label, 9, 0, 1, 2)
-        grid_lay.addWidget(self.grb_object_combo, 10, 0, 1, 2)
-
-        # ## Insert Copper Fiducial
-        self.add_cfid_button = QtWidgets.QPushButton(_("Add Fiducial"))
-        self.add_cfid_button.setToolTip(
-            _("Will add a polygon on the copper layer to serve as fiducial.")
-        )
-        self.add_cfid_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay.addWidget(self.add_cfid_button, 11, 0, 1, 2)
-
-        separator_line_2 = QtWidgets.QFrame()
-        separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line_2, 12, 0, 1, 2)
-
-        # Soldermask Gerber object #
-        self.sm_object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Soldermask Gerber"))
-        self.sm_object_label.setToolTip(
-            _("The Soldermask Gerber object.")
-        )
-        self.sm_object_combo = FCComboBox()
-        self.sm_object_combo.setModel(self.app.collection)
-        self.sm_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.sm_object_combo.is_last = True
-        self.sm_object_combo.obj_type = "Gerber"
-
-        grid_lay.addWidget(self.sm_object_label, 13, 0, 1, 2)
-        grid_lay.addWidget(self.sm_object_combo, 14, 0, 1, 2)
-
-        # ## Insert Soldermask opening for Fiducial
-        self.add_sm_opening_button = QtWidgets.QPushButton(_("Add Soldermask Opening"))
-        self.add_sm_opening_button.setToolTip(
-            _("Will add a polygon on the soldermask layer\n"
-              "to serve as fiducial opening.\n"
-              "The diameter is always double of the diameter\n"
-              "for the copper fiducial.")
-        )
-        self.add_sm_opening_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid_lay.addWidget(self.add_sm_opening_button, 15, 0, 1, 2)
-
-        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)
-
-        # Objects involved in Copper thieving
-        self.grb_object = None
-        self.sm_object = None
-
-        self.copper_obj_set = set()
-        self.sm_obj_set = set()
-
-        # store the flattened geometry here:
-        self.flat_geometry = []
-
-        # Events ID
-        self.mr = None
-        self.mm = None
-
-        # Mouse cursor positions
-        self.cursor_pos = (0, 0)
-        self.first_click = False
-
-        self.mode_method = False
-
-        # Tool properties
-        self.fid_dia = None
-        self.sm_opening_dia = None
-
-        self.margin_val = None
-        self.sec_position = None
-
-        self.grb_steps_per_circle = self.app.defaults["gerber_circle_steps"]
-
-        self.click_points = []
-
-        # SIGNALS
-        self.add_cfid_button.clicked.connect(self.add_fiducials)
-        self.add_sm_opening_button.clicked.connect(self.add_soldermask_opening)
-
-        self.fid_type_radio.activated_custom.connect(self.on_fiducial_type)
-        self.pos_radio.activated_custom.connect(self.on_second_point)
-        self.mode_radio.activated_custom.connect(self.on_method_change)
-        self.reset_button.clicked.connect(self.set_tool_ui)
-
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolFiducials()")
-
-        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, _("Fiducials Tool"))
-
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Alt+F', **kwargs)
-
-    def set_tool_ui(self):
-        self.units = self.app.defaults['units']
-        self.fid_size_entry.set_value(self.app.defaults["tools_fiducials_dia"])
-        self.margin_entry.set_value(float(self.app.defaults["tools_fiducials_margin"]))
-        self.mode_radio.set_value(self.app.defaults["tools_fiducials_mode"])
-        self.pos_radio.set_value(self.app.defaults["tools_fiducials_second_pos"])
-        self.fid_type_radio.set_value(self.app.defaults["tools_fiducials_type"])
-        self.line_thickness_entry.set_value(float(self.app.defaults["tools_fiducials_line_thickness"]))
-
-        self.click_points = []
-        self.bottom_left_coords_entry.set_value('')
-        self.top_right_coords_entry.set_value('')
-        self.sec_points_coords_entry.set_value('')
-
-        self.copper_obj_set = set()
-        self.sm_obj_set = set()
-
-    def on_second_point(self, val):
-        if val == 'no':
-            self.id_item_3.setFlags(QtCore.Qt.NoItemFlags)
-            self.sec_point_coords_lbl.setFlags(QtCore.Qt.NoItemFlags)
-            self.sec_points_coords_entry.setDisabled(True)
-        else:
-            self.id_item_3.setFlags(QtCore.Qt.ItemIsEnabled)
-            self.sec_point_coords_lbl.setFlags(QtCore.Qt.ItemIsEnabled)
-            self.sec_points_coords_entry.setDisabled(False)
-
-    def on_method_change(self, val):
-        """
-        Make sure that on method change we disconnect the event handlers and reset the points storage
-        :param val: value of the Radio button which trigger this method
-        :return: None
-        """
-        if val == 'auto':
-            self.click_points = []
-
-            try:
-                self.disconnect_event_handlers()
-            except TypeError:
-                pass
-
-    def on_fiducial_type(self, val):
-        if val == 'cross':
-            self.line_thickness_label.setDisabled(False)
-            self.line_thickness_entry.setDisabled(False)
-        else:
-            self.line_thickness_label.setDisabled(True)
-            self.line_thickness_entry.setDisabled(True)
-
-    def add_fiducials(self):
-        self.app.call_source = "fiducials_tool"
-        self.mode_method = self.mode_radio.get_value()
-        self.margin_val = self.margin_entry.get_value()
-        self.sec_position = self.pos_radio.get_value()
-        fid_type = self.fid_type_radio.get_value()
-
-        self.click_points = []
-
-        # get the Gerber object on which the Fiducial will be inserted
-        selection_index = self.grb_object_combo.currentIndex()
-        model_index = self.app.collection.index(selection_index, 0, self.grb_object_combo.rootModelIndex())
+        # get the Gerber object on which the Fiducial will be inserted
+        selection_index = self.ui.grb_object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.ui.grb_object_combo.rootModelIndex())
 
         try:
             self.grb_object = model_index.internalPointer().obj
         except Exception as e:
             log.debug("ToolFiducials.execute() --> %s" % str(e))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
-            return 'fail'
+            return
 
         self.copper_obj_set.add(self.grb_object.options['name'])
 
@@ -485,7 +201,7 @@ class ToolFiducials(AppTool):
                     float('%.*f' % (self.decimals, y0))
                 )
             )
-            self.bottom_left_coords_entry.set_value('(%.*f, %.*f)' % (self.decimals, x0, self.decimals, y0))
+            self.ui.bottom_left_coords_entry.set_value('(%.*f, %.*f)' % (self.decimals, x0, self.decimals, y0))
 
             self.click_points.append(
                 (
@@ -493,7 +209,7 @@ class ToolFiducials(AppTool):
                     float('%.*f' % (self.decimals, y1))
                 )
             )
-            self.top_right_coords_entry.set_value('(%.*f, %.*f)' % (self.decimals, x1, self.decimals, y1))
+            self.ui.top_right_coords_entry.set_value('(%.*f, %.*f)' % (self.decimals, x1, self.decimals, y1))
 
             if self.sec_position == 'up':
                 self.click_points.append(
@@ -502,7 +218,7 @@ class ToolFiducials(AppTool):
                         float('%.*f' % (self.decimals, y1))
                     )
                 )
-                self.sec_points_coords_entry.set_value('(%.*f, %.*f)' % (self.decimals, x0, self.decimals, y1))
+                self.ui.sec_points_coords_entry.set_value('(%.*f, %.*f)' % (self.decimals, x0, self.decimals, y1))
             elif self.sec_position == 'down':
                 self.click_points.append(
                     (
@@ -510,7 +226,7 @@ class ToolFiducials(AppTool):
                         float('%.*f' % (self.decimals, y0))
                     )
                 )
-                self.sec_points_coords_entry.set_value('(%.*f, %.*f)' % (self.decimals, x1, self.decimals, y0))
+                self.ui.sec_points_coords_entry.set_value('(%.*f, %.*f)' % (self.decimals, x1, self.decimals, y0))
 
             self.add_fiducials_geo(self.click_points, g_obj=self.grb_object, fid_type=fid_type)
             self.grb_object.source_file = self.app.export_gerber(obj_name=self.grb_object.options['name'],
@@ -519,9 +235,9 @@ class ToolFiducials(AppTool):
             self.on_exit()
         else:
             self.app.inform.emit(_("Click to add first Fiducial. Bottom Left..."))
-            self.bottom_left_coords_entry.set_value('')
-            self.top_right_coords_entry.set_value('')
-            self.sec_points_coords_entry.set_value('')
+            self.ui.bottom_left_coords_entry.set_value('')
+            self.ui.top_right_coords_entry.set_value('')
+            self.ui.sec_points_coords_entry.set_value('')
 
             self.connect_event_handlers()
 
@@ -537,9 +253,9 @@ class ToolFiducials(AppTool):
         :param line_size: the line thickenss when the fiducial type is cross
         :return:
         """
-        fid_size = self.fid_size_entry.get_value() if fid_size is None else fid_size
+        fid_size = self.ui.fid_size_entry.get_value() if fid_size is None else fid_size
         fid_type = 'circular' if fid_type is None else fid_type
-        line_thickness = self.line_thickness_entry.get_value() if line_size is None else line_size
+        line_thickness = self.ui.line_thickness_entry.get_value() if line_size is None else line_size
 
         radius = fid_size / 2.0
 
@@ -735,18 +451,18 @@ class ToolFiducials(AppTool):
             g_obj.solid_geometry = MultiPolygon(s_list)
 
     def add_soldermask_opening(self):
-        sm_opening_dia = self.fid_size_entry.get_value() * 2.0
+        sm_opening_dia = self.ui.fid_size_entry.get_value() * 2.0
 
         # get the Gerber object on which the Fiducial will be inserted
-        selection_index = self.sm_object_combo.currentIndex()
-        model_index = self.app.collection.index(selection_index, 0, self.sm_object_combo.rootModelIndex())
+        selection_index = self.ui.sm_object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.ui.sm_object_combo.rootModelIndex())
 
         try:
             self.sm_object = model_index.internalPointer().obj
         except Exception as e:
             log.debug("ToolFiducials.add_soldermask_opening() --> %s" % str(e))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
-            return 'fail'
+            return
 
         self.sm_obj_set.add(self.sm_object.options['name'])
         self.add_fiducials_geo(self.click_points, g_obj=self.sm_object, fid_size=sm_opening_dia, fid_type='circular')
@@ -781,15 +497,15 @@ class ToolFiducials(AppTool):
         fid_type = self.fid_type_radio.get_value()
 
         if len(self.click_points) == 1:
-            self.bottom_left_coords_entry.set_value(self.click_points[0])
+            self.ui.bottom_left_coords_entry.set_value(self.click_points[0])
             self.app.inform.emit(_("Click to add the last fiducial. Top Right..."))
 
         if self.sec_position != 'no':
             if len(self.click_points) == 2:
-                self.top_right_coords_entry.set_value(self.click_points[1])
+                self.ui.top_right_coords_entry.set_value(self.click_points[1])
                 self.app.inform.emit(_("Click to add the second fiducial. Top Left or Bottom Right..."))
             elif len(self.click_points) == 3:
-                self.sec_points_coords_entry.set_value(self.click_points[2])
+                self.ui.sec_points_coords_entry.set_value(self.click_points[2])
                 self.app.inform.emit('[success] %s' % _("Done. All fiducials have been added."))
                 self.add_fiducials_geo(self.click_points, g_obj=self.grb_object, fid_type=fid_type)
                 self.grb_object.source_file = self.app.export_gerber(obj_name=self.grb_object.options['name'],
@@ -798,7 +514,7 @@ class ToolFiducials(AppTool):
                 self.on_exit()
         else:
             if len(self.click_points) == 2:
-                self.top_right_coords_entry.set_value(self.click_points[1])
+                self.ui.top_right_coords_entry.set_value(self.click_points[1])
                 self.app.inform.emit('[success] %s' % _("Done. All fiducials have been added."))
                 self.add_fiducials_geo(self.click_points, g_obj=self.grb_object, fid_type=fid_type)
                 self.grb_object.source_file = self.app.export_gerber(obj_name=self.grb_object.options['name'],
@@ -925,3 +641,323 @@ class ToolFiducials(AppTool):
             self.flat_geometry.append(geometry)
 
         return self.flat_geometry
+
+
+class FidoUI:
+
+    toolName = _("Fiducials Tool")
+
+    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;
+                                }
+                                """)
+        self.layout.addWidget(title_label)
+        self.layout.addWidget(QtWidgets.QLabel(""))
+
+        self.points_label = QtWidgets.QLabel('<b>%s:</b>' % _('Fiducials Coordinates'))
+        self.points_label.setToolTip(
+            _("A table with the fiducial points coordinates,\n"
+              "in the format (x, y).")
+        )
+        self.layout.addWidget(self.points_label)
+
+        self.points_table = FCTable()
+        self.points_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+
+        self.points_table.setColumnCount(3)
+        self.points_table.setHorizontalHeaderLabels(
+            [
+                '#',
+                _("Name"),
+                _("Coordinates"),
+            ]
+        )
+        self.points_table.setRowCount(3)
+        row = 0
+        flags = QtCore.Qt.ItemIsEnabled
+
+        # BOTTOM LEFT
+        id_item_1 = QtWidgets.QTableWidgetItem('%d' % 1)
+        id_item_1.setFlags(flags)
+        self.points_table.setItem(row, 0, id_item_1)  # Tool name/id
+
+        self.bottom_left_coords_lbl = QtWidgets.QTableWidgetItem('%s' % _('Bottom Left'))
+        self.bottom_left_coords_lbl.setFlags(flags)
+        self.points_table.setItem(row, 1, self.bottom_left_coords_lbl)
+        self.bottom_left_coords_entry = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.bottom_left_coords_entry)
+        row += 1
+
+        # TOP RIGHT
+        id_item_2 = QtWidgets.QTableWidgetItem('%d' % 2)
+        id_item_2.setFlags(flags)
+        self.points_table.setItem(row, 0, id_item_2)  # Tool name/id
+
+        self.top_right_coords_lbl = QtWidgets.QTableWidgetItem('%s' % _('Top Right'))
+        self.top_right_coords_lbl.setFlags(flags)
+        self.points_table.setItem(row, 1, self.top_right_coords_lbl)
+        self.top_right_coords_entry = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.top_right_coords_entry)
+        row += 1
+
+        # Second Point
+        self.id_item_3 = QtWidgets.QTableWidgetItem('%d' % 3)
+        self.id_item_3.setFlags(flags)
+        self.points_table.setItem(row, 0, self.id_item_3)  # Tool name/id
+
+        self.sec_point_coords_lbl = QtWidgets.QTableWidgetItem('%s' % _('Second Point'))
+        self.sec_point_coords_lbl.setFlags(flags)
+        self.points_table.setItem(row, 1, self.sec_point_coords_lbl)
+        self.sec_points_coords_entry = EvalEntry()
+        self.points_table.setCellWidget(row, 2, self.sec_points_coords_entry)
+
+        vertical_header = self.points_table.verticalHeader()
+        vertical_header.hide()
+        self.points_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+        horizontal_header = self.points_table.horizontalHeader()
+        horizontal_header.setMinimumSectionSize(10)
+        horizontal_header.setDefaultSectionSize(70)
+
+        self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
+        # for x in range(4):
+        #     self.points_table.resizeColumnToContents(x)
+        self.points_table.resizeColumnsToContents()
+        self.points_table.resizeRowsToContents()
+
+        horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.resizeSection(0, 20)
+        horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
+        horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
+
+        self.points_table.setMinimumHeight(self.points_table.getHeight() + 2)
+        self.points_table.setMaximumHeight(self.points_table.getHeight() + 2)
+
+        # remove the frame on the QLineEdit childrens of the table
+        for row in range(self.points_table.rowCount()):
+            self.points_table.cellWidget(row, 2).setFrame(False)
+
+        self.layout.addWidget(self.points_table)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.layout.addWidget(separator_line)
+
+        # ## Grid Layout
+        grid_lay = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay)
+        grid_lay.setColumnStretch(0, 0)
+        grid_lay.setColumnStretch(1, 1)
+
+        self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
+        self.param_label.setToolTip(
+            _("Parameters used for this tool.")
+        )
+        grid_lay.addWidget(self.param_label, 0, 0, 1, 2)
+
+        # DIAMETER #
+        self.size_label = QtWidgets.QLabel('%s:' % _("Size"))
+        self.size_label.setToolTip(
+            _("This set the fiducial diameter if fiducial type is circular,\n"
+              "otherwise is the size of the fiducial.\n"
+              "The soldermask opening is double than that.")
+        )
+        self.fid_size_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.fid_size_entry.set_range(1.0000, 3.0000)
+        self.fid_size_entry.set_precision(self.decimals)
+        self.fid_size_entry.setWrapping(True)
+        self.fid_size_entry.setSingleStep(0.1)
+
+        grid_lay.addWidget(self.size_label, 1, 0)
+        grid_lay.addWidget(self.fid_size_entry, 1, 1)
+
+        # MARGIN #
+        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
+        self.margin_label.setToolTip(
+            _("Bounding box margin.")
+        )
+        self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.margin_entry.set_range(-9999.9999, 9999.9999)
+        self.margin_entry.set_precision(self.decimals)
+        self.margin_entry.setSingleStep(0.1)
+
+        grid_lay.addWidget(self.margin_label, 2, 0)
+        grid_lay.addWidget(self.margin_entry, 2, 1)
+
+        # Mode #
+        self.mode_radio = RadioSet([
+            {'label': _('Auto'), 'value': 'auto'},
+            {"label": _("Manual"), "value": "manual"}
+        ], stretch=False)
+        self.mode_label = QtWidgets.QLabel(_("Mode:"))
+        self.mode_label.setToolTip(
+            _("- 'Auto' - automatic placement of fiducials in the corners of the bounding box.\n "
+              "- 'Manual' - manual placement of fiducials.")
+        )
+        grid_lay.addWidget(self.mode_label, 3, 0)
+        grid_lay.addWidget(self.mode_radio, 3, 1)
+
+        # Position for second fiducial #
+        self.pos_radio = RadioSet([
+            {'label': _('Up'), 'value': 'up'},
+            {"label": _("Down"), "value": "down"},
+            {"label": _("None"), "value": "no"}
+        ], stretch=False)
+        self.pos_label = QtWidgets.QLabel('%s:' % _("Second fiducial"))
+        self.pos_label.setToolTip(
+            _("The position for the second fiducial.\n"
+              "- 'Up' - the order is: bottom-left, top-left, top-right.\n"
+              "- 'Down' - the order is: bottom-left, bottom-right, top-right.\n"
+              "- 'None' - there is no second fiducial. The order is: bottom-left, top-right.")
+        )
+        grid_lay.addWidget(self.pos_label, 4, 0)
+        grid_lay.addWidget(self.pos_radio, 4, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line, 5, 0, 1, 2)
+
+        # Fiducial type #
+        self.fid_type_radio = RadioSet([
+            {'label': _('Circular'), 'value': 'circular'},
+            {"label": _("Cross"), "value": "cross"},
+            {"label": _("Chess"), "value": "chess"}
+        ], stretch=False)
+        self.fid_type_label = QtWidgets.QLabel('%s:' % _("Fiducial Type"))
+        self.fid_type_label.setToolTip(
+            _("The type of fiducial.\n"
+              "- 'Circular' - this is the regular fiducial.\n"
+              "- 'Cross' - cross lines fiducial.\n"
+              "- 'Chess' - chess pattern fiducial.")
+        )
+        grid_lay.addWidget(self.fid_type_label, 6, 0)
+        grid_lay.addWidget(self.fid_type_radio, 6, 1)
+
+        # Line Thickness #
+        self.line_thickness_label = QtWidgets.QLabel('%s:' % _("Line thickness"))
+        self.line_thickness_label.setToolTip(
+            _("Thickness of the line that makes the fiducial.")
+        )
+        self.line_thickness_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.line_thickness_entry.set_range(0.00001, 9999.9999)
+        self.line_thickness_entry.set_precision(self.decimals)
+        self.line_thickness_entry.setSingleStep(0.1)
+
+        grid_lay.addWidget(self.line_thickness_label, 7, 0)
+        grid_lay.addWidget(self.line_thickness_entry, 7, 1)
+
+        separator_line_1 = QtWidgets.QFrame()
+        separator_line_1.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line_1.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line_1, 8, 0, 1, 2)
+
+        # Copper Gerber object
+        self.grb_object_combo = FCComboBox()
+        self.grb_object_combo.setModel(self.app.collection)
+        self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.grb_object_combo.is_last = True
+        self.grb_object_combo.obj_type = "Gerber"
+
+        self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
+        self.grbobj_label.setToolTip(
+            _("Gerber Object to which will be added a copper thieving.")
+        )
+
+        grid_lay.addWidget(self.grbobj_label, 9, 0, 1, 2)
+        grid_lay.addWidget(self.grb_object_combo, 10, 0, 1, 2)
+
+        # ## Insert Copper Fiducial
+        self.add_cfid_button = QtWidgets.QPushButton(_("Add Fiducial"))
+        self.add_cfid_button.setToolTip(
+            _("Will add a polygon on the copper layer to serve as fiducial.")
+        )
+        self.add_cfid_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        grid_lay.addWidget(self.add_cfid_button, 11, 0, 1, 2)
+
+        separator_line_2 = QtWidgets.QFrame()
+        separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line_2, 12, 0, 1, 2)
+
+        # Soldermask Gerber object #
+        self.sm_object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Soldermask Gerber"))
+        self.sm_object_label.setToolTip(
+            _("The Soldermask Gerber object.")
+        )
+        self.sm_object_combo = FCComboBox()
+        self.sm_object_combo.setModel(self.app.collection)
+        self.sm_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.sm_object_combo.is_last = True
+        self.sm_object_combo.obj_type = "Gerber"
+
+        grid_lay.addWidget(self.sm_object_label, 13, 0, 1, 2)
+        grid_lay.addWidget(self.sm_object_combo, 14, 0, 1, 2)
+
+        # ## Insert Soldermask opening for Fiducial
+        self.add_sm_opening_button = QtWidgets.QPushButton(_("Add Soldermask Opening"))
+        self.add_sm_opening_button.setToolTip(
+            _("Will add a polygon on the soldermask layer\n"
+              "to serve as fiducial opening.\n"
+              "The diameter is always double of the diameter\n"
+              "for the copper fiducial.")
+        )
+        self.add_sm_opening_button.setStyleSheet("""
+                                QPushButton
+                                {
+                                    font-weight: bold;
+                                }
+                                """)
+        grid_lay.addWidget(self.add_sm_opening_button, 15, 0, 1, 2)
+
+        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)
+
+        # #################################### 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)