Преглед изворни кода

- in Extract Drill Tool added a new method of drills extraction. The methods are: fixed diameter, fixed annular ring and proportional
- in Align Objects Tool finished the Single Point method of alignment

Marius Stanciu пре 6 година
родитељ
комит
9a3f3b600b
5 измењених фајлова са 329 додато и 86 уклоњено
  1. 19 13
      FlatCAMApp.py
  2. 5 0
      README.md
  3. 50 22
      flatcamGUI/PreferencesUI.py
  4. 76 36
      flatcamTools/ToolAlignObjects.py
  5. 179 15
      flatcamTools/ToolExtractDrills.py

+ 19 - 13
FlatCAMApp.py

@@ -957,6 +957,7 @@ class App(QtCore.QObject):
             # Drills Extraction Tool
             # Drills Extraction Tool
             "tools_edrills_hole_type": 'fixed',
             "tools_edrills_hole_type": 'fixed',
             "tools_edrills_hole_fixed_dia": 0.5,
             "tools_edrills_hole_fixed_dia": 0.5,
+            "tools_edrills_hole_prop_factor": 80.0,
             "tools_edrills_circular_ring": 0.2,
             "tools_edrills_circular_ring": 0.2,
             "tools_edrills_oblong_ring": 0.2,
             "tools_edrills_oblong_ring": 0.2,
             "tools_edrills_square_ring": 0.2,
             "tools_edrills_square_ring": 0.2,
@@ -1598,6 +1599,7 @@ class App(QtCore.QObject):
             # Extract Drills Tool
             # Extract Drills Tool
             "tools_edrills_hole_type": self.ui.tools2_defaults_form.tools2_edrills_group.hole_size_radio,
             "tools_edrills_hole_type": self.ui.tools2_defaults_form.tools2_edrills_group.hole_size_radio,
             "tools_edrills_hole_fixed_dia": self.ui.tools2_defaults_form.tools2_edrills_group.dia_entry,
             "tools_edrills_hole_fixed_dia": self.ui.tools2_defaults_form.tools2_edrills_group.dia_entry,
+            "tools_edrills_hole_prop_factor": self.ui.tools2_defaults_form.tools2_edrills_group.factor_entry,
             "tools_edrills_circular_ring": self.ui.tools2_defaults_form.tools2_edrills_group.circular_ring_entry,
             "tools_edrills_circular_ring": self.ui.tools2_defaults_form.tools2_edrills_group.circular_ring_entry,
             "tools_edrills_oblong_ring": self.ui.tools2_defaults_form.tools2_edrills_group.oblong_ring_entry,
             "tools_edrills_oblong_ring": self.ui.tools2_defaults_form.tools2_edrills_group.oblong_ring_entry,
             "tools_edrills_square_ring": self.ui.tools2_defaults_form.tools2_edrills_group.square_ring_entry,
             "tools_edrills_square_ring": self.ui.tools2_defaults_form.tools2_edrills_group.square_ring_entry,
@@ -4277,9 +4279,20 @@ class App(QtCore.QObject):
                 obj.options['xmax'] = xmax
                 obj.options['xmax'] = xmax
                 obj.options['ymax'] = ymax
                 obj.options['ymax'] = ymax
             except Exception as e:
             except Exception as e:
-                log.warning("The object has no bounds properties. %s" % str(e))
+                log.warning("App.new_object() -> The object has no bounds properties. %s" % str(e))
                 return "fail"
                 return "fail"
 
 
+            try:
+                if kind == 'excellon':
+                    obj.fill_color = self.app.defaults["excellon_plot_fill"]
+                    obj.outline_color = self.app.defaults["excellon_plot_line"]
+
+                if kind == 'gerber':
+                    obj.fill_color = self.app.defaults["gerber_plot_fill"]
+                    obj.outline_color = self.app.defaults["gerber_plot_line"]
+            except Exception as e:
+                log.warning("App.new_object() -> setting colors error. %s" % str(e))
+
         # update the KeyWords list with the name of the file
         # update the KeyWords list with the name of the file
         self.myKeywords.append(obj.options['name'])
         self.myKeywords.append(obj.options['name'])
 
 
@@ -12305,19 +12318,12 @@ class App(QtCore.QObject):
         new_line_color = color_variant(new_color[:7], 0.7)
         new_line_color = color_variant(new_color[:7], 0.7)
 
 
         for sel_obj in sel_obj_list:
         for sel_obj in sel_obj_list:
-            if self.is_legacy is False:
-                sel_obj.fill_color = new_color
-                sel_obj.outline_color = new_line_color
+            sel_obj.fill_color = new_color
+            sel_obj.outline_color = new_line_color
 
 
-                sel_obj.shapes.redraw(
-                    update_colors=(new_color, new_line_color)
-                )
-            else:
-                sel_obj.fill_color = new_color
-                sel_obj.outline_color = new_line_color
-                sel_obj.shapes.redraw(
-                    update_colors=(new_color, new_line_color)
-                )
+            sel_obj.shapes.redraw(
+                update_colors=(new_color, new_line_color)
+            )
 
 
     def on_grid_snap_triggered(self, state):
     def on_grid_snap_triggered(self, state):
         if state:
         if state:

+ 5 - 0
README.md

@@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+14.01.2020
+
+- in Extract Drill Tool added a new method of drills extraction. The methods are: fixed diameter, fixed annular ring and proportional
+- in Align Objects Tool finished the Single Point method of alignment
+
 13.01.2020
 13.01.2020
 
 
 - fixed a small GUI issue in Excellon UI when Basic mode is active
 - fixed a small GUI issue in Excellon UI when Basic mode is active

+ 50 - 22
flatcamGUI/PreferencesUI.py

@@ -7697,14 +7697,20 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
         grid_lay.addWidget(separator_line, 8, 0, 1, 2)
         grid_lay.addWidget(separator_line, 8, 0, 1, 2)
 
 
         # ## Axis
         # ## Axis
-        self.hole_size_radio = RadioSet([{'label': _("Fixed"), 'value': 'fixed'},
-                                         {'label': _("Proportional"), 'value': 'prop'}])
-        self.hole_size_label = QtWidgets.QLabel('%s:' % _("Hole Size"))
+        self.hole_size_radio = RadioSet(
+            [
+                {'label': _("Fixed Diameter"), 'value': 'fixed'},
+                {'label': _("Fixed Annular Ring"), 'value': 'ring'},
+                {'label': _("Proportional"), 'value': 'prop'}
+            ],
+            orientation='vertical',
+            stretch=False)
+        self.hole_size_label = QtWidgets.QLabel('<b>%s:</b>' % _("Method"))
         self.hole_size_label.setToolTip(
         self.hole_size_label.setToolTip(
-            _("The type of hole size. Can be:\n"
-              "- Fixed -> all holes will have a set size\n"
-              "- Proprotional -> each hole will havea a variable size\n"
-              "such as to preserve a set annular ring"))
+            _("The selected method of extracting the drills. Can be:\n"
+              "- Fixed Diameter -> all holes will have a set size\n"
+              "- Fixed Annular Ring -> all holes will have a set annular ring\n"
+              "- Proportional -> each hole size will be a fraction of the pad size"))
 
 
         grid_lay.addWidget(self.hole_size_label, 9, 0)
         grid_lay.addWidget(self.hole_size_label, 9, 0)
         grid_lay.addWidget(self.hole_size_radio, 9, 1)
         grid_lay.addWidget(self.hole_size_radio, 9, 1)
@@ -7716,27 +7722,31 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         grid_lay.addWidget(separator_line, 10, 0, 1, 2)
         grid_lay.addWidget(separator_line, 10, 0, 1, 2)
 
 
+        # Annular Ring
+        self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
+        grid_lay.addWidget(self.fixed_label, 11, 0, 1, 2)
+
         # Diameter value
         # Diameter value
         self.dia_entry = FCDoubleSpinner()
         self.dia_entry = FCDoubleSpinner()
         self.dia_entry.set_precision(self.decimals)
         self.dia_entry.set_precision(self.decimals)
         self.dia_entry.set_range(0.0000, 9999.9999)
         self.dia_entry.set_range(0.0000, 9999.9999)
 
 
-        self.dia_label = QtWidgets.QLabel('%s:' % _("Diameter"))
+        self.dia_label = QtWidgets.QLabel('%s:' % _("value"))
         self.dia_label.setToolTip(
         self.dia_label.setToolTip(
             _("Fixed hole diameter.")
             _("Fixed hole diameter.")
         )
         )
 
 
-        grid_lay.addWidget(self.dia_label, 11, 0)
-        grid_lay.addWidget(self.dia_entry, 11, 1)
+        grid_lay.addWidget(self.dia_label, 12, 0)
+        grid_lay.addWidget(self.dia_entry, 12, 1)
 
 
         # Annular Ring value
         # Annular Ring value
-        self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Annular Ring"))
+        self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
         self.ring_label.setToolTip(
         self.ring_label.setToolTip(
             _("The size of annular ring.\n"
             _("The size of annular ring.\n"
               "The copper sliver between the drill hole exterior\n"
               "The copper sliver between the drill hole exterior\n"
               "and the margin of the copper pad.")
               "and the margin of the copper pad.")
         )
         )
-        grid_lay.addWidget(self.ring_label, 12, 0, 1, 2)
+        grid_lay.addWidget(self.ring_label, 13, 0, 1, 2)
 
 
         # Circular Annular Ring Value
         # Circular Annular Ring Value
         self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
         self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
@@ -7748,8 +7758,8 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
         self.circular_ring_entry.set_precision(self.decimals)
         self.circular_ring_entry.set_precision(self.decimals)
         self.circular_ring_entry.set_range(0.0000, 9999.9999)
         self.circular_ring_entry.set_range(0.0000, 9999.9999)
 
 
-        grid_lay.addWidget(self.circular_ring_label, 13, 0)
-        grid_lay.addWidget(self.circular_ring_entry, 13, 1)
+        grid_lay.addWidget(self.circular_ring_label, 14, 0)
+        grid_lay.addWidget(self.circular_ring_entry, 14, 1)
 
 
         # Oblong Annular Ring Value
         # Oblong Annular Ring Value
         self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong"))
         self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong"))
@@ -7761,8 +7771,8 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
         self.oblong_ring_entry.set_precision(self.decimals)
         self.oblong_ring_entry.set_precision(self.decimals)
         self.oblong_ring_entry.set_range(0.0000, 9999.9999)
         self.oblong_ring_entry.set_range(0.0000, 9999.9999)
 
 
-        grid_lay.addWidget(self.oblong_ring_label, 14, 0)
-        grid_lay.addWidget(self.oblong_ring_entry, 14, 1)
+        grid_lay.addWidget(self.oblong_ring_label, 15, 0)
+        grid_lay.addWidget(self.oblong_ring_entry, 15, 1)
 
 
         # Square Annular Ring Value
         # Square Annular Ring Value
         self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
         self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
@@ -7774,8 +7784,8 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
         self.square_ring_entry.set_precision(self.decimals)
         self.square_ring_entry.set_precision(self.decimals)
         self.square_ring_entry.set_range(0.0000, 9999.9999)
         self.square_ring_entry.set_range(0.0000, 9999.9999)
 
 
-        grid_lay.addWidget(self.square_ring_label, 15, 0)
-        grid_lay.addWidget(self.square_ring_entry, 15, 1)
+        grid_lay.addWidget(self.square_ring_label, 16, 0)
+        grid_lay.addWidget(self.square_ring_entry, 16, 1)
 
 
         # Rectangular Annular Ring Value
         # Rectangular Annular Ring Value
         self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
         self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
@@ -7787,8 +7797,8 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
         self.rectangular_ring_entry.set_precision(self.decimals)
         self.rectangular_ring_entry.set_precision(self.decimals)
         self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
         self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
 
 
-        grid_lay.addWidget(self.rectangular_ring_label, 16, 0)
-        grid_lay.addWidget(self.rectangular_ring_entry, 16, 1)
+        grid_lay.addWidget(self.rectangular_ring_label, 17, 0)
+        grid_lay.addWidget(self.rectangular_ring_entry, 17, 1)
 
 
         # Others Annular Ring Value
         # Others Annular Ring Value
         self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
         self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
@@ -7800,8 +7810,26 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
         self.other_ring_entry.set_precision(self.decimals)
         self.other_ring_entry.set_precision(self.decimals)
         self.other_ring_entry.set_range(0.0000, 9999.9999)
         self.other_ring_entry.set_range(0.0000, 9999.9999)
 
 
-        grid_lay.addWidget(self.other_ring_label, 17, 0)
-        grid_lay.addWidget(self.other_ring_entry, 17, 1)
+        grid_lay.addWidget(self.other_ring_label, 18, 0)
+        grid_lay.addWidget(self.other_ring_entry, 18, 1)
+
+        self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
+        grid_lay.addWidget(self.prop_label, 19, 0, 1, 2)
+
+        # Factor value
+        self.factor_entry = FCDoubleSpinner(suffix='%')
+        self.factor_entry.set_precision(self.decimals)
+        self.factor_entry.set_range(0.0000, 100.0000)
+        self.factor_entry.setSingleStep(0.1)
+
+        self.factor_label = QtWidgets.QLabel('%s:' % _("Factor"))
+        self.factor_label.setToolTip(
+            _("Proportional Diameter.\n"
+              "The drill diameter will be a fraction of the pad size.")
+        )
+
+        grid_lay.addWidget(self.factor_label, 20, 0)
+        grid_lay.addWidget(self.factor_entry, 20, 1)
 
 
         self.layout.addStretch()
         self.layout.addStretch()
 
 

+ 76 - 36
flatcamTools/ToolAlignObjects.py

@@ -54,18 +54,16 @@ class AlignObjects(FlatCAMTool):
         grid0.setColumnStretch(1, 1)
         grid0.setColumnStretch(1, 1)
         self.layout.addLayout(grid0)
         self.layout.addLayout(grid0)
 
 
-        self.aligned_label = QtWidgets.QLabel('<b>%s</b>' % _("Selection of the aligned object"))
+        self.aligned_label = QtWidgets.QLabel('<b>%s</b>' % _("Selection of the WORKING object"))
         grid0.addWidget(self.aligned_label, 0, 0, 1, 2)
         grid0.addWidget(self.aligned_label, 0, 0, 1, 2)
 
 
         # Type of object to be aligned
         # Type of object to be aligned
         self.type_obj_combo = FCComboBox()
         self.type_obj_combo = FCComboBox()
         self.type_obj_combo.addItem("Gerber")
         self.type_obj_combo.addItem("Gerber")
         self.type_obj_combo.addItem("Excellon")
         self.type_obj_combo.addItem("Excellon")
-        self.type_obj_combo.addItem("Geometry")
 
 
         self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
         self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
         self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
         self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
-        self.type_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
 
         self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.type_obj_combo_label.setToolTip(
         self.type_obj_combo_label.setToolTip(
@@ -96,9 +94,9 @@ class AlignObjects(FlatCAMTool):
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         grid0.addWidget(separator_line, 4, 0, 1, 2)
         grid0.addWidget(separator_line, 4, 0, 1, 2)
 
 
-        self.aligned_label = QtWidgets.QLabel('<b>%s</b>' % _("Selection of the aligner object"))
+        self.aligned_label = QtWidgets.QLabel('<b>%s</b>' % _("Selection of the TARGET object"))
         self.aligned_label.setToolTip(
         self.aligned_label.setToolTip(
-            _("Object to which the other objects will be aligned to (moved).")
+            _("Object to which the other objects will be aligned to (moved to).")
         )
         )
         grid0.addWidget(self.aligned_label, 6, 0, 1, 2)
         grid0.addWidget(self.aligned_label, 6, 0, 1, 2)
 
 
@@ -106,11 +104,9 @@ class AlignObjects(FlatCAMTool):
         self.type_aligner_obj_combo = FCComboBox()
         self.type_aligner_obj_combo = FCComboBox()
         self.type_aligner_obj_combo.addItem("Gerber")
         self.type_aligner_obj_combo.addItem("Gerber")
         self.type_aligner_obj_combo.addItem("Excellon")
         self.type_aligner_obj_combo.addItem("Excellon")
-        self.type_aligner_obj_combo.addItem("Geometry")
 
 
         self.type_aligner_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
         self.type_aligner_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
         self.type_aligner_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
         self.type_aligner_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
-        self.type_aligner_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
 
         self.type_aligner_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.type_aligner_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.type_aligner_obj_combo_label.setToolTip(
         self.type_aligner_obj_combo_label.setToolTip(
@@ -219,10 +215,17 @@ class AlignObjects(FlatCAMTool):
         self.clicked_points = list()
         self.clicked_points = list()
 
 
         self.new_start = None
         self.new_start = None
-        self.new_stop = None
+        self.new_dest = None
 
 
         self.align_type = None
         self.align_type = None
 
 
+        # old colors of objects involved in the alignment
+        self.aligner_old_fill_color = None
+        self.aligner_old_line_color = None
+        self.aligned_old_fill_color = None
+        self.aligned_old_line_color = None
+
+
     def run(self, toggle=True):
     def run(self, toggle=True):
         self.app.report_usage("ToolAlignObjects()")
         self.app.report_usage("ToolAlignObjects()")
 
 
@@ -261,6 +264,11 @@ class AlignObjects(FlatCAMTool):
         self.aligned_obj = None
         self.aligned_obj = None
         self.aligner_obj = None
         self.aligner_obj = None
 
 
+        self.aligner_old_fill_color = None
+        self.aligner_old_line_color = None
+        self.aligned_old_fill_color = None
+        self.aligned_old_line_color = None
+
         self.a_type_radio.set_value(self.app.defaults["tools_align_objects_align_type"])
         self.a_type_radio.set_value(self.app.defaults["tools_align_objects_align_type"])
 
 
         if self.local_connected is True:
         if self.local_connected is True:
@@ -277,6 +285,7 @@ class AlignObjects(FlatCAMTool):
         self.aligner_object_combo.setCurrentIndex(0)
         self.aligner_object_combo.setCurrentIndex(0)
 
 
     def on_align(self):
     def on_align(self):
+        self.app.delete_selection_shape()
 
 
         obj_sel_index = self.object_combo.currentIndex()
         obj_sel_index = self.object_combo.currentIndex()
         obj_model_index = self.app.collection.index(obj_sel_index, 0, self.object_combo.rootModelIndex())
         obj_model_index = self.app.collection.index(obj_sel_index, 0, self.object_combo.rootModelIndex())
@@ -314,8 +323,14 @@ class AlignObjects(FlatCAMTool):
 
 
         self.local_connected = True
         self.local_connected = True
 
 
-        self.app.inform.emit(_("Get First alignment point on the aligned object."))
+        self.aligner_old_fill_color = self.aligner_obj.fill_color
+        self.aligner_old_line_color = self.aligner_obj.outline_color
+        self.aligned_old_fill_color = self.aligned_obj.fill_color
+        self.aligned_old_line_color = self.aligned_obj.outline_color
+
+        self.app.inform.emit('%s: %s' % (_("First Point"), _("Click on the START point.")))
         self.target_obj = self.aligned_obj
         self.target_obj = self.aligned_obj
+        self.set_color()
 
 
     def on_mouse_click_release(self, event):
     def on_mouse_click_release(self, event):
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
@@ -365,40 +380,47 @@ class AlignObjects(FlatCAMTool):
                                         self.check_points()
                                         self.check_points()
 
 
         elif event.button == right_button and self.app.event_is_dragging is False:
         elif event.button == right_button and self.app.event_is_dragging is False:
+            self.reset_color()
             self.clicked_points = list()
             self.clicked_points = list()
             self.disconnect_cal_events()
             self.disconnect_cal_events()
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request."))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request."))
 
 
     def check_points(self):
     def check_points(self):
-        if self.align_type == 'sp':
-            if len(self.clicked_points) == 1:
-                self.app.inform.emit(_("Get First alignment point on the aligner object."))
-                self.target_obj = self.aligner_obj
-
-            if len(self.clicked_points) == 2:
+        if len(self.clicked_points) == 1:
+            self.app.inform.emit('%s: %s. %s' % (
+                _("First Point"), _("Click on the DESTINATION point."), _(" Or right click to cancel.")))
+            self.target_obj = self.aligner_obj
+            self.reset_color()
+            self.set_color()
+
+        if len(self.clicked_points) == 2:
+            self.align_translate()
+            if self.align_type == 'sp':
                 self.app.inform.emit('[success] %s' % _("Done."))
                 self.app.inform.emit('[success] %s' % _("Done."))
-                self.align_translate()
                 self.app.plot_all()
                 self.app.plot_all()
 
 
                 self.disconnect_cal_events()
                 self.disconnect_cal_events()
-        else:
-            if len(self.clicked_points) == 1:
-                self.app.inform.emit(_("Get Second alignment point on aligned object. Or right click to cancel."))
-
-            if len(self.clicked_points) == 2:
-                self.app.inform.emit(_("Get First alignment point on the aligner object."))
-                self.target_obj = self.aligner_obj
-
-            if len(self.clicked_points) == 3:
-                self.app.inform.emit(_("Get Second alignment point on the aligner object. Or right click to cancel."))
-
-            if len(self.clicked_points) == 4:
-                self.app.inform.emit('[success] %s' % _("Done."))
-                self.align_translate()
-                self.align_rotate()
-                self.app.plot_all()
+                return
+            else:
+                self.app.inform.emit('%s: %s. %s' % (
+                    _("Second Point"), _("Click on the START point."), _(" Or right click to cancel.")))
+                self.target_obj = self.aligned_obj
+                self.reset_color()
+                self.set_color()
+
+        if len(self.clicked_points) == 3:
+            self.app.inform.emit('%s: %s. %s' % (
+                _("Second Point"), _("Click on the DESTINATION point."), _(" Or right click to cancel.")))
+            self.target_obj = self.aligner_obj
+            self.reset_color()
+            self.set_color()
+
+        if len(self.clicked_points) == 4:
+            self.align_rotate()
+            self.app.inform.emit('[success] %s' % _("Done."))
 
 
-                self.disconnect_cal_events()
+            self.disconnect_cal_events()
+            self.app.plot_all()
 
 
     def align_translate(self):
     def align_translate(self):
         dx = self.clicked_points[1][0] - self.clicked_points[0][0]
         dx = self.clicked_points[1][0] - self.clicked_points[0][0]
@@ -477,9 +499,27 @@ class AlignObjects(FlatCAMTool):
             self.canvas.graph_event_disconnect(self.mr)
             self.canvas.graph_event_disconnect(self.mr)
 
 
         self.local_connected = False
         self.local_connected = False
-        self.target_obj = None
-        self.aligned_obj = None
-        self.aligner_obj = None
+
+        self.aligner_old_fill_color = None
+        self.aligner_old_line_color = None
+        self.aligned_old_fill_color = None
+        self.aligned_old_line_color = None
+
+    def set_color(self):
+        new_color = "#15678abf"
+        new_line_color = new_color
+        self.target_obj.shapes.redraw(
+            update_colors=(new_color, new_line_color)
+        )
+
+    def reset_color(self):
+        self.aligned_obj.shapes.redraw(
+            update_colors=(self.aligned_old_fill_color, self.aligned_old_line_color)
+        )
+
+        self.aligner_obj.shapes.redraw(
+            update_colors=(self.aligner_old_fill_color, self.aligner_old_line_color)
+        )
 
 
     def reset_fields(self):
     def reset_fields(self):
         self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 179 - 15
flatcamTools/ToolExtractDrills.py

@@ -125,15 +125,25 @@ class ToolExtractDrills(FlatCAMTool):
         grid1.setColumnStretch(0, 0)
         grid1.setColumnStretch(0, 0)
         grid1.setColumnStretch(1, 1)
         grid1.setColumnStretch(1, 1)
 
 
+        self.method_label = QtWidgets.QLabel('<b>%s</b>' % _("Method"))
+        grid1.addWidget(self.method_label, 2, 0, 1, 2)
+
         # ## Axis
         # ## Axis
-        self.hole_size_radio = RadioSet([{'label': _("Fixed"), 'value': 'fixed'},
-                                         {'label': _("Proportional"), 'value': 'prop'}])
+        self.hole_size_radio = RadioSet(
+            [
+                {'label': _("Fixed Diameter"), 'value': 'fixed'},
+                {'label': _("Fixed Annular Ring"), 'value': 'ring'},
+                {'label': _("Proportional"), 'value': 'prop'}
+            ],
+            orientation='vertical',
+            stretch=False)
+
         self.hole_size_label = QtWidgets.QLabel('%s:' % _("Hole Size"))
         self.hole_size_label = QtWidgets.QLabel('%s:' % _("Hole Size"))
         self.hole_size_label.setToolTip(
         self.hole_size_label.setToolTip(
-            _("The type of hole size. Can be:\n"
-              "- Fixed -> all holes will have a set size\n"
-              "- Proprotional -> each hole will havea a variable size\n"
-              "such as to preserve a set annular ring"))
+            _("The selected method of extracting the drills. Can be:\n"
+              "- Fixed Diameter -> all holes will have a set size\n"
+              "- Fixed Annular Ring -> all holes will have a set annular ring\n"
+              "- Proportional -> each hole size will be a fraction of the pad size"))
 
 
         grid1.addWidget(self.hole_size_label, 3, 0)
         grid1.addWidget(self.hole_size_label, 3, 0)
         grid1.addWidget(self.hole_size_radio, 3, 1)
         grid1.addWidget(self.hole_size_radio, 3, 1)
@@ -145,18 +155,27 @@ class ToolExtractDrills(FlatCAMTool):
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         grid1.addWidget(separator_line, 5, 0, 1, 2)
         grid1.addWidget(separator_line, 5, 0, 1, 2)
 
 
+        # Annular Ring
+        self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
+        grid1.addWidget(self.fixed_label, 6, 0, 1, 2)
+
         # Diameter value
         # Diameter value
         self.dia_entry = FCDoubleSpinner()
         self.dia_entry = FCDoubleSpinner()
         self.dia_entry.set_precision(self.decimals)
         self.dia_entry.set_precision(self.decimals)
         self.dia_entry.set_range(0.0000, 9999.9999)
         self.dia_entry.set_range(0.0000, 9999.9999)
 
 
-        self.dia_label = QtWidgets.QLabel('%s:' % _("Diameter"))
+        self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
         self.dia_label.setToolTip(
         self.dia_label.setToolTip(
             _("Fixed hole diameter.")
             _("Fixed hole diameter.")
         )
         )
 
 
-        grid1.addWidget(self.dia_label, 7, 0)
-        grid1.addWidget(self.dia_entry, 7, 1)
+        grid1.addWidget(self.dia_label, 8, 0)
+        grid1.addWidget(self.dia_entry, 8, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid1.addWidget(separator_line, 9, 0, 1, 2)
 
 
         self.ring_frame = QtWidgets.QFrame()
         self.ring_frame = QtWidgets.QFrame()
         self.ring_frame.setContentsMargins(0, 0, 0, 0)
         self.ring_frame.setContentsMargins(0, 0, 0, 0)
@@ -173,7 +192,7 @@ class ToolExtractDrills(FlatCAMTool):
         self.ring_box.addLayout(grid2)
         self.ring_box.addLayout(grid2)
 
 
         # Annular Ring value
         # Annular Ring value
-        self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Annular Ring"))
+        self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
         self.ring_label.setToolTip(
         self.ring_label.setToolTip(
             _("The size of annular ring.\n"
             _("The size of annular ring.\n"
               "The copper sliver between the drill hole exterior\n"
               "The copper sliver between the drill hole exterior\n"
@@ -246,6 +265,35 @@ class ToolExtractDrills(FlatCAMTool):
         grid2.addWidget(self.other_ring_label, 5, 0)
         grid2.addWidget(self.other_ring_label, 5, 0)
         grid2.addWidget(self.other_ring_entry, 5, 1)
         grid2.addWidget(self.other_ring_entry, 5, 1)
 
 
+        grid3 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid3)
+        grid3.setColumnStretch(0, 0)
+        grid3.setColumnStretch(1, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid3.addWidget(separator_line, 1, 0, 1, 2)
+
+        # Annular Ring value
+        self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
+        grid3.addWidget(self.prop_label, 2, 0, 1, 2)
+
+        # Diameter value
+        self.factor_entry = FCDoubleSpinner(suffix='%')
+        self.factor_entry.set_precision(self.decimals)
+        self.factor_entry.set_range(0.0000, 100.0000)
+        self.factor_entry.setSingleStep(0.1)
+
+        self.factor_label = QtWidgets.QLabel('%s:' % _("Value"))
+        self.factor_label.setToolTip(
+            _("Proportional Diameter.\n"
+              "The drill diameter will be a fraction of the pad size.")
+        )
+
+        grid3.addWidget(self.factor_label, 3, 0)
+        grid3.addWidget(self.factor_entry, 3, 1)
+
         # Extract drills from Gerber apertures flashes (pads)
         # Extract drills from Gerber apertures flashes (pads)
         self.e_drills_button = QtWidgets.QPushButton(_("Extract Drills"))
         self.e_drills_button = QtWidgets.QPushButton(_("Extract Drills"))
         self.e_drills_button.setToolTip(
         self.e_drills_button.setToolTip(
@@ -280,6 +328,13 @@ class ToolExtractDrills(FlatCAMTool):
         self.rectangular_ring_entry.setEnabled(False)
         self.rectangular_ring_entry.setEnabled(False)
         self.other_ring_entry.setEnabled(False)
         self.other_ring_entry.setEnabled(False)
 
 
+        self.dia_entry.setDisabled(True)
+        self.dia_label.setDisabled(True)
+        self.factor_label.setDisabled(True)
+        self.factor_entry.setDisabled(True)
+
+        self.ring_frame.setDisabled(True)
+
         # ## Signals
         # ## Signals
         self.hole_size_radio.activated_custom.connect(self.on_hole_size_toggle)
         self.hole_size_radio.activated_custom.connect(self.on_hole_size_toggle)
         self.e_drills_button.clicked.connect(self.on_extract_drills_click)
         self.e_drills_button.clicked.connect(self.on_extract_drills_click)
@@ -359,6 +414,8 @@ class ToolExtractDrills(FlatCAMTool):
         self.rectangular_cb.set_value(self.app.defaults["tools_edrills_rectangular"])
         self.rectangular_cb.set_value(self.app.defaults["tools_edrills_rectangular"])
         self.other_cb.set_value(self.app.defaults["tools_edrills_others"])
         self.other_cb.set_value(self.app.defaults["tools_edrills_others"])
 
 
+        self.factor_entry.set_value(float(self.app.defaults["tools_edrills_hole_prop_factor"]))
+
     def on_extract_drills_click(self):
     def on_extract_drills_click(self):
 
 
         drill_dia = self.dia_entry.get_value()
         drill_dia = self.dia_entry.get_value()
@@ -368,6 +425,8 @@ class ToolExtractDrills(FlatCAMTool):
         rect_r_val = self.rectangular_ring_entry.get_value()
         rect_r_val = self.rectangular_ring_entry.get_value()
         other_r_val = self.other_ring_entry.get_value()
         other_r_val = self.other_ring_entry.get_value()
 
 
+        prop_factor = self.factor_entry.get_value() / 100.0
+
         drills = list()
         drills = list()
         tools = dict()
         tools = dict()
 
 
@@ -376,7 +435,7 @@ class ToolExtractDrills(FlatCAMTool):
 
 
         try:
         try:
             fcobj = model_index.internalPointer().obj
             fcobj = model_index.internalPointer().obj
-        except Exception as e:
+        except Exception:
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
             return
             return
 
 
@@ -421,7 +480,7 @@ class ToolExtractDrills(FlatCAMTool):
             if 'solid_geometry' not in tools["1"] or not tools["1"]['solid_geometry']:
             if 'solid_geometry' not in tools["1"] or not tools["1"]['solid_geometry']:
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
                 return
                 return
-        else:
+        elif mode == 'ring':
             drills_found = set()
             drills_found = set()
             for apid, apid_value in fcobj.apertures.items():
             for apid, apid_value in fcobj.apertures.items():
                 ap_type = apid_value['type']
                 ap_type = apid_value['type']
@@ -435,9 +494,9 @@ class ToolExtractDrills(FlatCAMTool):
                     height = float(apid_value['height'])
                     height = float(apid_value['height'])
                     if self.oblong_cb.get_value():
                     if self.oblong_cb.get_value():
                         if width > height:
                         if width > height:
-                            dia = float(apid_value['height']) - (2 * rect_r_val)
+                            dia = float(apid_value['height']) - (2 * oblong_r_val)
                         else:
                         else:
-                            dia = float(apid_value['width']) - (2 * rect_r_val)
+                            dia = float(apid_value['width']) - (2 * oblong_r_val)
                 elif ap_type == 'R':
                 elif ap_type == 'R':
                     width = float(apid_value['width'])
                     width = float(apid_value['width'])
                     height = float(apid_value['height'])
                     height = float(apid_value['height'])
@@ -506,6 +565,91 @@ class ToolExtractDrills(FlatCAMTool):
             if True not in drills_found:
             if True not in drills_found:
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
                 return
                 return
+        else:
+            drills_found = set()
+            for apid, apid_value in fcobj.apertures.items():
+                ap_type = apid_value['type']
+
+                dia = None
+                if ap_type == 'C':
+                    if self.circular_cb.get_value():
+                        dia = float(apid_value['size']) * prop_factor
+                elif ap_type == 'O':
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+                    if self.oblong_cb.get_value():
+                        if width > height:
+                            dia = float(apid_value['height']) * prop_factor
+                        else:
+                            dia = float(apid_value['width']) * prop_factor
+                elif ap_type == 'R':
+                    width = float(apid_value['width'])
+                    height = float(apid_value['height'])
+
+                    # if the height == width (float numbers so the reason for the following)
+                    if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
+                            (10 ** -self.decimals):
+                        if self.square_cb.get_value():
+                            dia = float(apid_value['height']) * prop_factor
+                    else:
+                        if self.rectangular_cb.get_value():
+                            if width > height:
+                                dia = float(apid_value['height']) * prop_factor
+                            else:
+                                dia = float(apid_value['width']) * prop_factor
+                else:
+                    if self.other_cb.get_value():
+                        try:
+                            dia = float(apid_value['size']) * prop_factor
+                        except KeyError:
+                            if ap_type == 'AM':
+                                pol = apid_value['geometry'][0]['solid']
+                                x0, y0, x1, y1 = pol.bounds
+                                dx = x1 - x0
+                                dy = y1 - y0
+                                if dx <= dy:
+                                    dia = dx * prop_factor
+                                else:
+                                    dia = dy * prop_factor
+
+                # if dia is None then none of the above applied so we skip the following
+                if dia is None:
+                    continue
+
+                tool_in_drills = False
+                for tool, tool_val in tools.items():
+                    if abs(float('%.*f' % (self.decimals, tool_val["C"])) - float('%.*f' % (self.decimals, dia))) < \
+                            (10 ** -self.decimals):
+                        tool_in_drills = tool
+
+                if tool_in_drills is False:
+                    if tools:
+                        new_tool = max([int(t) for t in tools]) + 1
+                        tool_in_drills = str(new_tool)
+                    else:
+                        tool_in_drills = "1"
+
+                for geo_el in apid_value['geometry']:
+                    if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
+                        if tool_in_drills not in tools:
+                            tools[tool_in_drills] = {"C": dia}
+
+                        drills.append({"point": geo_el['follow'], "tool": tool_in_drills})
+
+                        if 'solid_geometry' not in tools[tool_in_drills]:
+                            tools[tool_in_drills]['solid_geometry'] = list()
+                        else:
+                            tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
+
+                if tool_in_drills in tools:
+                    if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
+                        drills_found.add(False)
+                    else:
+                        drills_found.add(True)
+
+            if True not in drills_found:
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
+                return
 
 
         def obj_init(obj_inst, app_inst):
         def obj_init(obj_inst, app_inst):
             obj_inst.tools = tools
             obj_inst.tools = tools
@@ -518,16 +662,36 @@ class ToolExtractDrills(FlatCAMTool):
 
 
     def on_hole_size_toggle(self, val):
     def on_hole_size_toggle(self, val):
         if val == "fixed":
         if val == "fixed":
+            self.fixed_label.setDisabled(False)
             self.dia_entry.setDisabled(False)
             self.dia_entry.setDisabled(False)
             self.dia_label.setDisabled(False)
             self.dia_label.setDisabled(False)
 
 
             self.ring_frame.setDisabled(True)
             self.ring_frame.setDisabled(True)
-        else:
+
+            self.prop_label.setDisabled(True)
+            self.factor_label.setDisabled(True)
+            self.factor_entry.setDisabled(True)
+        elif val == "ring":
+            self.fixed_label.setDisabled(True)
             self.dia_entry.setDisabled(True)
             self.dia_entry.setDisabled(True)
             self.dia_label.setDisabled(True)
             self.dia_label.setDisabled(True)
 
 
             self.ring_frame.setDisabled(False)
             self.ring_frame.setDisabled(False)
 
 
+            self.prop_label.setDisabled(True)
+            self.factor_label.setDisabled(True)
+            self.factor_entry.setDisabled(True)
+        elif val == "prop":
+            self.fixed_label.setDisabled(True)
+            self.dia_entry.setDisabled(True)
+            self.dia_label.setDisabled(True)
+
+            self.ring_frame.setDisabled(True)
+
+            self.prop_label.setDisabled(False)
+            self.factor_label.setDisabled(False)
+            self.factor_entry.setDisabled(False)
+
     def reset_fields(self):
     def reset_fields(self):
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.gerber_object_combo.setCurrentIndex(0)
         self.gerber_object_combo.setCurrentIndex(0)