diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 16c98005..7f3596e3 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -9,7 +9,6 @@ import urllib.request import urllib.parse import urllib.error -import webbrowser import getopt import random @@ -141,7 +140,7 @@ class App(QtCore.QObject): # ################## Version and VERSION DATE ############################## # ########################################################################## version = 8.992 - version_date = "2020/02/12" + version_date = "2020/02/22" beta = True engine = '3D' @@ -985,6 +984,21 @@ class App(QtCore.QObject): "tools_edrills_rectangular": False, "tools_edrills_others": False, + # Punch Gerber Tool + "tools_punch_hole_type": 'exc', + "tools_punch_hole_fixed_dia": 0.5, + "tools_punch_hole_prop_factor": 80.0, + "tools_punch_circular_ring": 0.2, + "tools_punch_oblong_ring": 0.2, + "tools_punch_square_ring": 0.2, + "tools_punch_rectangular_ring": 0.2, + "tools_punch_others_ring": 0.2, + "tools_punch_circular": True, + "tools_punch_oblong": False, + "tools_punch_square": True, + "tools_punch_rectangular": False, + "tools_punch_others": False, + # Align Objects Tool "tools_align_objects_align_type": 'sp', @@ -1637,6 +1651,21 @@ class App(QtCore.QObject): "tools_edrills_rectangular": self.ui.tools2_defaults_form.tools2_edrills_group.rectangular_cb, "tools_edrills_others": self.ui.tools2_defaults_form.tools2_edrills_group.other_cb, + # Punch Gerber Tool + "tools_punch_hole_type": self.ui.tools2_defaults_form.tools2_punch_group.hole_size_radio, + "tools_punch_hole_fixed_dia": self.ui.tools2_defaults_form.tools2_punch_group.dia_entry, + "tools_punch_hole_prop_factor": self.ui.tools2_defaults_form.tools2_punch_group.factor_entry, + "tools_punch_circular_ring": self.ui.tools2_defaults_form.tools2_punch_group.circular_ring_entry, + "tools_punch_oblong_ring": self.ui.tools2_defaults_form.tools2_punch_group.oblong_ring_entry, + "tools_punch_square_ring": self.ui.tools2_defaults_form.tools2_punch_group.square_ring_entry, + "tools_punch_rectangular_ring": self.ui.tools2_defaults_form.tools2_punch_group.rectangular_ring_entry, + "tools_punch_others_ring": self.ui.tools2_defaults_form.tools2_punch_group.other_ring_entry, + "tools_punch_circular": self.ui.tools2_defaults_form.tools2_punch_group.circular_cb, + "tools_punch_oblong": self.ui.tools2_defaults_form.tools2_punch_group.oblong_cb, + "tools_punch_square": self.ui.tools2_defaults_form.tools2_punch_group.square_cb, + "tools_punch_rectangular": self.ui.tools2_defaults_form.tools2_punch_group.rectangular_cb, + "tools_punch_others": self.ui.tools2_defaults_form.tools2_punch_group.other_cb, + # Utilities # File associations "fa_excellon": self.ui.util_defaults_form.fa_excellon_group.exc_list_text, @@ -3459,7 +3488,7 @@ class App(QtCore.QObject): edited_obj.options['xmax'] = xmax edited_obj.options['ymax'] = ymax except AttributeError as e: - self.inform.emit('[WARNING] %s' % _("Object empty after edit.")) + self.inform.emit('[WARNING] %s' % _("Object empty after edit.")) log.debug("App.editor2object() --> Geometry --> %s" % str(e)) edited_obj.build_ui() @@ -5972,7 +6001,6 @@ class App(QtCore.QObject): tools_diameters[t] *= sfactor self.defaults['geometry_cnctooldia'] += "%.*f," % (self.decimals, tools_diameters[t]) elif dim == 'tools_ncctools': - ncctools = list() if type(self.defaults["tools_ncctools"]) == float: ncctools = [self.defaults["tools_ncctools"]] else: @@ -5988,7 +6016,6 @@ class App(QtCore.QObject): ncctools[t] *= sfactor self.defaults['tools_ncctools'] += "%.*f," % (self.decimals, ncctools[t]) elif dim == 'tools_solderpaste_tools': - sptools = list() if type(self.defaults["tools_solderpaste_tools"]) == float: sptools = [self.defaults["tools_solderpaste_tools"]] else: @@ -6017,7 +6044,6 @@ class App(QtCore.QObject): elif dim == 'global_gridx' or dim == 'global_gridy': if new_units == 'IN': - val = 0.1 try: val = float(self.defaults[dim]) * sfactor except Exception as e: @@ -6026,7 +6052,6 @@ class App(QtCore.QObject): self.defaults[dim] = float('%.*f' % (self.decimals, val)) else: - val = 0.1 try: val = float(self.defaults[dim]) * sfactor except Exception as e: @@ -6035,7 +6060,6 @@ class App(QtCore.QObject): self.defaults[dim] = float('%.*f' % (self.decimals, val)) else: - val = 0.1 if self.defaults[dim]: try: val = float(self.defaults[dim]) * sfactor @@ -7149,7 +7173,7 @@ class App(QtCore.QObject): if self.collection.get_active(): self.log.debug("App.on_delete()") - for obj_active in self.collection.get_selected(): + for obj_active in self.collection.get_selected(): # if the deleted object is FlatCAMGerber then make sure to delete the possible mark shapes if isinstance(obj_active, FlatCAMGerber): for el in obj_active.mark_shapes: @@ -7692,6 +7716,7 @@ class App(QtCore.QObject): obj_init.follow_geometry = deepcopy(obj.follow_geometry) except AttributeError: pass + try: obj_init.apertures = deepcopy(obj.apertures) except AttributeError: @@ -7725,8 +7750,8 @@ class App(QtCore.QObject): self.new_object("gerber", str(obj_name) + custom_name, initialize_gerber) elif isinstance(obj, FlatCAMGeometry): self.new_object("geometry", str(obj_name) + custom_name, initialize_geometry) - except Exception as e: - return "Operation failed: %s" % str(e) + except Exception as er: + return "Operation failed: %s" % str(er) def on_rename_object(self, text): self.report_usage("on_rename_object()") @@ -8761,7 +8786,7 @@ class App(QtCore.QObject): try: # May fail in case mouse not within axes pos_canvas = self.plotcanvas.translate_coords(event_pos) - if self.grid_status() == True: + if self.grid_status(): pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1]) # Update cursor @@ -8847,7 +8872,7 @@ class App(QtCore.QObject): right_button = 3 pos_canvas = self.plotcanvas.translate_coords(event_pos) - if self.grid_status() == True: + if self.grid_status(): pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1]) else: pos = (pos_canvas[0], pos_canvas[1]) @@ -10446,8 +10471,8 @@ class App(QtCore.QObject): try: filename, _f = QtWidgets.QFileDialog.getSaveFileName( caption=_("Save Project As ..."), - directory=('{l_save}/{proj}_{date}').format(l_save=str(self.get_last_save_folder()), date=self.date, - proj=_("Project")), + directory='{l_save}/{proj}_{date}'.format(l_save=str(self.get_last_save_folder()), date=self.date, + proj=_("Project")), filter=filter_ ) except TypeError: @@ -10500,9 +10525,9 @@ class App(QtCore.QObject): try: filename, _f = QtWidgets.QFileDialog.getSaveFileName( caption=_("Save Object as PDF ..."), - directory=('{l_save}/{obj_name}_{date}').format(l_save=str(self.get_last_save_folder()), - obj_name=obj_name, - date=self.date), + directory='{l_save}/{obj_name}_{date}'.format(l_save=str(self.get_last_save_folder()), + obj_name=obj_name, + date=self.date), filter=filter_ ) except TypeError: @@ -11484,7 +11509,7 @@ class App(QtCore.QObject): # # ## Object creation # ## ret = self.new_object("geometry", name, obj_init, autoselected=False) if ret == 'fail': - self.inform.emit('[ERROR_NOTCL]%s' % _(' Open HPGL2 failed. Probable not a HPGL2 file.')) + self.inform.emit('[ERROR_NOTCL]%s' % _(' Open HPGL2 failed. Probable not a HPGL2 file.')) return 'fail' # Register recent file diff --git a/README.md b/README.md index 4f826947..8ab918e5 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,16 @@ CAD program, and create G-Code for Isolation routing. ================================================= -12.02.2020 +13.02.2020 +- finished Punch Gerber Tool + +12.02.2020 - working on fixing a bug in FlatCAMGeometry.merge() - FIXED issue #380 - fixed bug: when deleting a FlatCAMCNCJob with annotations enabled, the annotations are not deleted from canvas; fixed issue #379 - fixed bug: creating a new project while a project is open and it contain CNCJob annotations and/or Gerber mark shapes, did not delete them from canvas - 11.02.2020 - working on Tool Punch; finished the geometry update with the clear geometry for the case of Excellon method diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index b67b4c0e..b8154db7 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -1559,6 +1559,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): ALT+E  %s + + ALT+H +  %s + ALT+I  %s @@ -1688,7 +1692,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): _("Skew on Y axis"), # ALT section _("Align Objects Tool"), _("Calculators Tool"), _("2-Sided PCB Tool"), _("Transformations Tool"), - _("Extract Drills Tool"), _("Fiducials Tool"), + _("Punch Gerber Tool"), _("Extract Drills Tool"), _("Fiducials Tool"), _("Solder Paste Dispensing Tool"), _("Film PCB Tool"), _("Non-Copper Clearing Tool"), _("Optimal Tool"), _("Paint Area Tool"), _("QRCode Tool"), _("Rules Check Tool"), @@ -2974,6 +2978,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): return # Align in Object Tool + if key == QtCore.Qt.Key_H: + self.app.punch_tool.run(toggle=True) + + # Extract Drills Tool if key == QtCore.Qt.Key_I: self.app.edrills_tool.run(toggle=True) diff --git a/flatcamGUI/PreferencesUI.py b/flatcamGUI/PreferencesUI.py index b003b71c..eeeb413a 100644 --- a/flatcamGUI/PreferencesUI.py +++ b/flatcamGUI/PreferencesUI.py @@ -249,6 +249,9 @@ class Tools2PreferencesUI(QtWidgets.QWidget): self.tools2_edrills_group = Tools2EDrillsPrefGroupUI(decimals=self.decimals) self.tools2_edrills_group.setMinimumWidth(220) + self.tools2_punch_group = Tools2PunchGerberPrefGroupUI(decimals=self.decimals) + self.tools2_punch_group.setMinimumWidth(220) + self.vlay = QtWidgets.QVBoxLayout() self.vlay.addWidget(self.tools2_checkrules_group) self.vlay.addWidget(self.tools2_optimal_group) @@ -264,10 +267,14 @@ class Tools2PreferencesUI(QtWidgets.QWidget): self.vlay3.addWidget(self.tools2_cal_group) self.vlay3.addWidget(self.tools2_edrills_group) + self.vlay4 = QtWidgets.QVBoxLayout() + self.vlay4.addWidget(self.tools2_punch_group) + self.layout.addLayout(self.vlay) self.layout.addLayout(self.vlay1) self.layout.addLayout(self.vlay2) self.layout.addLayout(self.vlay3) + self.layout.addLayout(self.vlay4) self.layout.addStretch() @@ -7838,7 +7845,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): # Circular Aperture Selection self.circular_cb = FCCheckBox('%s' % _("Circular")) self.circular_cb.setToolTip( - _("Create drills from circular pads.") + _("Process Circular Pads.") ) grid_lay.addWidget(self.circular_cb, 3, 0, 1, 2) @@ -7846,7 +7853,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): # Oblong Aperture Selection self.oblong_cb = FCCheckBox('%s' % _("Oblong")) self.oblong_cb.setToolTip( - _("Create drills from oblong pads.") + _("Process Oblong Pads.") ) grid_lay.addWidget(self.oblong_cb, 4, 0, 1, 2) @@ -7854,7 +7861,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): # Square Aperture Selection self.square_cb = FCCheckBox('%s' % _("Square")) self.square_cb.setToolTip( - _("Create drills from square pads.") + _("Process Square Pads.") ) grid_lay.addWidget(self.square_cb, 5, 0, 1, 2) @@ -7862,7 +7869,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): # Rectangular Aperture Selection self.rectangular_cb = FCCheckBox('%s' % _("Rectangular")) self.rectangular_cb.setToolTip( - _("Create drills from rectangular pads.") + _("Process Rectangular Pads.") ) grid_lay.addWidget(self.rectangular_cb, 6, 0, 1, 2) @@ -7870,7 +7877,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): # Others type of Apertures Selection self.other_cb = FCCheckBox('%s' % _("Others")) self.other_cb.setToolTip( - _("Create drills from other types of pad shape.") + _("Process pads not in the categories above.") ) grid_lay.addWidget(self.other_cb, 7, 0, 1, 2) @@ -7891,7 +7898,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): stretch=False) self.hole_size_label = QtWidgets.QLabel('%s:' % _("Method")) self.hole_size_label.setToolTip( - _("The selected method of extracting the drills. Can be:\n" + _("The method for processing pads. 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")) @@ -7915,7 +7922,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): self.dia_entry.set_precision(self.decimals) self.dia_entry.set_range(0.0000, 9999.9999) - self.dia_label = QtWidgets.QLabel('%s:' % _("value")) + self.dia_label = QtWidgets.QLabel('%s:' % _("Value")) self.dia_label.setToolTip( _("Fixed hole diameter.") ) @@ -7927,7 +7934,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): self.ring_label = QtWidgets.QLabel('%s' % _("Fixed Annular Ring")) self.ring_label.setToolTip( _("The size of annular ring.\n" - "The copper sliver between the drill hole exterior\n" + "The copper sliver between the hole exterior\n" "and the margin of the copper pad.") ) grid_lay.addWidget(self.ring_label, 13, 0, 1, 2) @@ -8009,7 +8016,221 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): 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.") + "The hole 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() + + +class Tools2PunchGerberPrefGroupUI(OptionsGroupUI): + def __init__(self, decimals=4, parent=None): + + super(Tools2PunchGerberPrefGroupUI, self).__init__(self) + + self.setTitle(str(_("Punch Gerber Options"))) + self.decimals = decimals + + # ## 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('%s:' % _('Parameters')) + self.param_label.setToolTip( + _("Parameters used for this tool.") + ) + grid_lay.addWidget(self.param_label, 0, 0, 1, 2) + + self.padt_label = QtWidgets.QLabel("%s:" % _("Processed Pads Type")) + self.padt_label.setToolTip( + _("The type of pads shape to be processed.\n" + "If the PCB has many SMD pads with rectangular pads,\n" + "disable the Rectangular aperture.") + ) + + grid_lay.addWidget(self.padt_label, 2, 0, 1, 2) + + # Circular Aperture Selection + self.circular_cb = FCCheckBox('%s' % _("Circular")) + self.circular_cb.setToolTip( + _("Process Circular Pads.") + ) + + grid_lay.addWidget(self.circular_cb, 3, 0, 1, 2) + + # Oblong Aperture Selection + self.oblong_cb = FCCheckBox('%s' % _("Oblong")) + self.oblong_cb.setToolTip( + _("Process Oblong Pads.") + ) + + grid_lay.addWidget(self.oblong_cb, 4, 0, 1, 2) + + # Square Aperture Selection + self.square_cb = FCCheckBox('%s' % _("Square")) + self.square_cb.setToolTip( + _("Process Square Pads.") + ) + + grid_lay.addWidget(self.square_cb, 5, 0, 1, 2) + + # Rectangular Aperture Selection + self.rectangular_cb = FCCheckBox('%s' % _("Rectangular")) + self.rectangular_cb.setToolTip( + _("Process Rectangular Pads.") + ) + + grid_lay.addWidget(self.rectangular_cb, 6, 0, 1, 2) + + # Others type of Apertures Selection + self.other_cb = FCCheckBox('%s' % _("Others")) + self.other_cb.setToolTip( + _("Process pads not in the categories above.") + ) + + grid_lay.addWidget(self.other_cb, 7, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid_lay.addWidget(separator_line, 8, 0, 1, 2) + + # ## Axis + self.hole_size_radio = RadioSet( + [ + {'label': _("Excellon"), 'value': 'exc'}, + {'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:' % _("Method")) + self.hole_size_label.setToolTip( + _("The punch hole source can be:\n" + "- Excellon Object-> the Excellon object drills center will serve as reference.\n" + "- Fixed Diameter -> will try to use the pads center as reference adding fixed diameter holes.\n" + "- Fixed Annular Ring -> will try to keep a set annular ring.\n" + "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.\n") + ) + grid_lay.addWidget(self.hole_size_label, 9, 0) + grid_lay.addWidget(self.hole_size_radio, 9, 1) + + # grid_lay1.addWidget(QtWidgets.QLabel('')) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid_lay.addWidget(separator_line, 10, 0, 1, 2) + + # Annular Ring + self.fixed_label = QtWidgets.QLabel('%s' % _("Fixed Diameter")) + grid_lay.addWidget(self.fixed_label, 11, 0, 1, 2) + + # Diameter value + self.dia_entry = FCDoubleSpinner() + self.dia_entry.set_precision(self.decimals) + self.dia_entry.set_range(0.0000, 9999.9999) + + self.dia_label = QtWidgets.QLabel('%s:' % _("Value")) + self.dia_label.setToolTip( + _("Fixed hole diameter.") + ) + + grid_lay.addWidget(self.dia_label, 12, 0) + grid_lay.addWidget(self.dia_entry, 12, 1) + + # Annular Ring value + self.ring_label = QtWidgets.QLabel('%s' % _("Fixed Annular Ring")) + self.ring_label.setToolTip( + _("The size of annular ring.\n" + "The copper sliver between the hole exterior\n" + "and the margin of the copper pad.") + ) + grid_lay.addWidget(self.ring_label, 13, 0, 1, 2) + + # Circular Annular Ring Value + self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular")) + self.circular_ring_label.setToolTip( + _("The size of annular ring for circular pads.") + ) + + self.circular_ring_entry = FCDoubleSpinner() + self.circular_ring_entry.set_precision(self.decimals) + self.circular_ring_entry.set_range(0.0000, 9999.9999) + + grid_lay.addWidget(self.circular_ring_label, 14, 0) + grid_lay.addWidget(self.circular_ring_entry, 14, 1) + + # Oblong Annular Ring Value + self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong")) + self.oblong_ring_label.setToolTip( + _("The size of annular ring for oblong pads.") + ) + + self.oblong_ring_entry = FCDoubleSpinner() + self.oblong_ring_entry.set_precision(self.decimals) + self.oblong_ring_entry.set_range(0.0000, 9999.9999) + + grid_lay.addWidget(self.oblong_ring_label, 15, 0) + grid_lay.addWidget(self.oblong_ring_entry, 15, 1) + + # Square Annular Ring Value + self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square")) + self.square_ring_label.setToolTip( + _("The size of annular ring for square pads.") + ) + + self.square_ring_entry = FCDoubleSpinner() + self.square_ring_entry.set_precision(self.decimals) + self.square_ring_entry.set_range(0.0000, 9999.9999) + + grid_lay.addWidget(self.square_ring_label, 16, 0) + grid_lay.addWidget(self.square_ring_entry, 16, 1) + + # Rectangular Annular Ring Value + self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular")) + self.rectangular_ring_label.setToolTip( + _("The size of annular ring for rectangular pads.") + ) + + self.rectangular_ring_entry = FCDoubleSpinner() + self.rectangular_ring_entry.set_precision(self.decimals) + self.rectangular_ring_entry.set_range(0.0000, 9999.9999) + + grid_lay.addWidget(self.rectangular_ring_label, 17, 0) + grid_lay.addWidget(self.rectangular_ring_entry, 17, 1) + + # Others Annular Ring Value + self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others")) + self.other_ring_label.setToolTip( + _("The size of annular ring for other pads.") + ) + + self.other_ring_entry = FCDoubleSpinner() + self.other_ring_entry.set_precision(self.decimals) + self.other_ring_entry.set_range(0.0000, 9999.9999) + + grid_lay.addWidget(self.other_ring_label, 18, 0) + grid_lay.addWidget(self.other_ring_entry, 18, 1) + + self.prop_label = QtWidgets.QLabel('%s' % _("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 hole diameter will be a fraction of the pad size.") ) grid_lay.addWidget(self.factor_label, 20, 0) diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py index 7fa1dc98..bc772e42 100644 --- a/flatcamTools/ToolExtractDrills.py +++ b/flatcamTools/ToolExtractDrills.py @@ -76,7 +76,7 @@ class ToolExtractDrills(FlatCAMTool): # Circular Aperture Selection self.circular_cb = FCCheckBox('%s' % _("Circular")) self.circular_cb.setToolTip( - _("Create drills from circular pads.") + _("Process Circular Pads.") ) grid_lay.addWidget(self.circular_cb, 3, 0, 1, 2) @@ -84,7 +84,7 @@ class ToolExtractDrills(FlatCAMTool): # Oblong Aperture Selection self.oblong_cb = FCCheckBox('%s' % _("Oblong")) self.oblong_cb.setToolTip( - _("Create drills from oblong pads.") + _("Process Oblong Pads.") ) grid_lay.addWidget(self.oblong_cb, 4, 0, 1, 2) @@ -92,7 +92,7 @@ class ToolExtractDrills(FlatCAMTool): # Square Aperture Selection self.square_cb = FCCheckBox('%s' % _("Square")) self.square_cb.setToolTip( - _("Create drills from square pads.") + _("Process Square Pads.") ) grid_lay.addWidget(self.square_cb, 5, 0, 1, 2) @@ -100,7 +100,7 @@ class ToolExtractDrills(FlatCAMTool): # Rectangular Aperture Selection self.rectangular_cb = FCCheckBox('%s' % _("Rectangular")) self.rectangular_cb.setToolTip( - _("Create drills from rectangular pads.") + _("Process Rectangular Pads.") ) grid_lay.addWidget(self.rectangular_cb, 6, 0, 1, 2) @@ -108,7 +108,7 @@ class ToolExtractDrills(FlatCAMTool): # Others type of Apertures Selection self.other_cb = FCCheckBox('%s' % _("Others")) self.other_cb.setToolTip( - _("Create drills from other types of pad shape.") + _("Process pads not in the categories above.") ) grid_lay.addWidget(self.other_cb, 7, 0, 1, 2) @@ -126,7 +126,7 @@ class ToolExtractDrills(FlatCAMTool): self.method_label = QtWidgets.QLabel('%s' % _("Method")) self.method_label.setToolTip( - _("The selected method of extracting the drills. Can be:\n" + _("The method for processing pads. 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")) @@ -191,7 +191,7 @@ class ToolExtractDrills(FlatCAMTool): self.ring_label = QtWidgets.QLabel('%s' % _("Fixed Annular Ring")) self.ring_label.setToolTip( _("The size of annular ring.\n" - "The copper sliver between the drill hole exterior\n" + "The copper sliver between the hole exterior\n" "and the margin of the copper pad.") ) grid2.addWidget(self.ring_label, 0, 0, 1, 2) @@ -284,7 +284,7 @@ class ToolExtractDrills(FlatCAMTool): 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.") + "The hole diameter will be a fraction of the pad size.") ) grid3.addWidget(self.factor_label, 3, 0) diff --git a/flatcamTools/ToolPunchGerber.py b/flatcamTools/ToolPunchGerber.py index 10bf4b92..f7378933 100644 --- a/flatcamTools/ToolPunchGerber.py +++ b/flatcamTools/ToolPunchGerber.py @@ -87,7 +87,7 @@ class ToolPunchGerber(FlatCAMTool): # Circular Aperture Selection self.circular_cb = FCCheckBox('%s' % _("Circular")) self.circular_cb.setToolTip( - _("Create drills from circular pads.") + _("Process Circular Pads.") ) grid_lay.addWidget(self.circular_cb, 5, 0, 1, 2) @@ -95,7 +95,7 @@ class ToolPunchGerber(FlatCAMTool): # Oblong Aperture Selection self.oblong_cb = FCCheckBox('%s' % _("Oblong")) self.oblong_cb.setToolTip( - _("Create drills from oblong pads.") + _("Process Oblong Pads.") ) grid_lay.addWidget(self.oblong_cb, 6, 0, 1, 2) @@ -103,7 +103,7 @@ class ToolPunchGerber(FlatCAMTool): # Square Aperture Selection self.square_cb = FCCheckBox('%s' % _("Square")) self.square_cb.setToolTip( - _("Create drills from square pads.") + _("Process Square Pads.") ) grid_lay.addWidget(self.square_cb, 7, 0, 1, 2) @@ -111,7 +111,7 @@ class ToolPunchGerber(FlatCAMTool): # Rectangular Aperture Selection self.rectangular_cb = FCCheckBox('%s' % _("Rectangular")) self.rectangular_cb.setToolTip( - _("Create drills from rectangular pads.") + _("Process Rectangular Pads.") ) grid_lay.addWidget(self.rectangular_cb, 8, 0, 1, 2) @@ -119,7 +119,7 @@ class ToolPunchGerber(FlatCAMTool): # Others type of Apertures Selection self.other_cb = FCCheckBox('%s' % _("Others")) self.other_cb.setToolTip( - _("Create drills from other types of pad shape.") + _("Process pads not in the categories above.") ) grid_lay.addWidget(self.other_cb, 9, 0, 1, 2) @@ -212,7 +212,7 @@ class ToolPunchGerber(FlatCAMTool): self.ring_label = QtWidgets.QLabel('%s' % _("Fixed Annular Ring")) self.ring_label.setToolTip( _("The size of annular ring.\n" - "The copper sliver between the drill hole exterior\n" + "The copper sliver between the hole exterior\n" "and the margin of the copper pad.") ) self.ring_box.addWidget(self.ring_label) @@ -306,7 +306,7 @@ class ToolPunchGerber(FlatCAMTool): 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.") + "The hole diameter will be a fraction of the pad size.") ) grid0.addWidget(self.factor_label, 13, 0) @@ -429,8 +429,24 @@ class ToolPunchGerber(FlatCAMTool): self.reset_fields() self.ui_connect() - self.method_punch.set_value('exc') - self.select_all_cb.set_value(True) + self.method_punch.set_value(self.app.defaults["tools_punch_hole_type"]) + self.select_all_cb.set_value(False) + + self.dia_entry.set_value(float(self.app.defaults["tools_punch_hole_fixed_dia"])) + + self.circular_ring_entry.set_value(float(self.app.defaults["tools_punch_circular_ring"])) + self.oblong_ring_entry.set_value(float(self.app.defaults["tools_punch_oblong_ring"])) + self.square_ring_entry.set_value(float(self.app.defaults["tools_punch_square_ring"])) + self.rectangular_ring_entry.set_value(float(self.app.defaults["tools_punch_rectangular_ring"])) + self.other_ring_entry.set_value(float(self.app.defaults["tools_punch_others_ring"])) + + self.circular_cb.set_value(self.app.defaults["tools_punch_circular"]) + self.oblong_cb.set_value(self.app.defaults["tools_punch_oblong"]) + self.square_cb.set_value(self.app.defaults["tools_punch_square"]) + self.rectangular_cb.set_value(self.app.defaults["tools_punch_rectangular"]) + self.other_cb.set_value(self.app.defaults["tools_punch_others"]) + + self.factor_entry.set_value(float(self.app.defaults["tools_punch_hole_prop_factor"])) def on_select_all(self, state): self.ui_disconnect() @@ -685,9 +701,286 @@ class ToolPunchGerber(FlatCAMTool): self.app.new_object('gerber', outname, init_func) elif punch_method == 'ring': - pass + circ_r_val = self.circular_ring_entry.get_value() + oblong_r_val = self.oblong_ring_entry.get_value() + square_r_val = self.square_ring_entry.get_value() + rect_r_val = self.rectangular_ring_entry.get_value() + other_r_val = self.other_ring_entry.get_value() + + dia = None + + if isinstance(grb_obj.solid_geometry, list): + temp_solid_geometry = MultiPolygon(grb_obj.solid_geometry) + else: + temp_solid_geometry = grb_obj.solid_geometry + + punched_solid_geometry = temp_solid_geometry + + new_apertures = deepcopy(grb_obj.apertures) + new_apertures_items = new_apertures.items() + + # find maximum aperture id + new_apid = max([int(x) for x, __ in new_apertures_items]) + + # store here the clear geometry, the key is the new aperture size + holes_apertures = dict() + + for apid, apid_value in grb_obj.apertures.items(): + ap_type = apid_value['type'] + punching_geo = list() + + if ap_type == 'C' and self.circular_cb.get_value(): + dia = float(apid_value['size']) - (2 * circ_r_val) + for elem in apid_value['geometry']: + if 'follow' in elem and isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + + elif ap_type == 'O' and self.oblong_cb.get_value(): + width = float(apid_value['width']) + height = float(apid_value['height']) + + if width > height: + dia = float(apid_value['height']) - (2 * oblong_r_val) + else: + dia = float(apid_value['width']) - (2 * oblong_r_val) + + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + + 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 round(width, self.decimals) == round(height, self.decimals): + if self.square_cb.get_value(): + dia = float(apid_value['height']) - (2 * square_r_val) + + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + elif self.rectangular_cb.get_value(): + if width > height: + dia = float(apid_value['height']) - (2 * rect_r_val) + else: + dia = float(apid_value['width']) - (2 * rect_r_val) + + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + + elif self.other_cb.get_value(): + try: + dia = float(apid_value['size']) - (2 * other_r_val) + 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 - (2 * other_r_val) + else: + dia = dy - (2 * other_r_val) + + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + + # if dia is None then none of the above applied so we skip the following + if dia is None: + continue + + punching_geo = MultiPolygon(punching_geo) + + if punching_geo is None or punching_geo.is_empty: + continue + + punched_solid_geometry = punched_solid_geometry.difference(punching_geo) + + # update the gerber apertures to include the clear geometry so it can be exported successfully + for elem in apid_value['geometry']: + # make it work only for Gerber Flashes who are Points in 'follow' + if 'solid' in elem and isinstance(elem['follow'], Point): + clear_apid_size = dia + for geo in punching_geo: + + # since there may be drills that do not drill into a pad we test only for geos in a pad + if geo.within(elem['solid']): + geo_elem = dict() + geo_elem['clear'] = geo.centroid + + if clear_apid_size not in holes_apertures: + holes_apertures[clear_apid_size] = dict() + holes_apertures[clear_apid_size]['type'] = 'C' + holes_apertures[clear_apid_size]['size'] = clear_apid_size + holes_apertures[clear_apid_size]['geometry'] = list() + + holes_apertures[clear_apid_size]['geometry'].append(deepcopy(geo_elem)) + + # add the clear geometry to new apertures; it's easier than to test if there are apertures with the same + # size and add there the clear geometry + for hole_size, ap_val in holes_apertures.items(): + new_apid += 1 + new_apertures[str(new_apid)] = deepcopy(ap_val) + + def init_func(new_obj, app_obj): + new_obj.options.update(grb_obj.options) + new_obj.options['name'] = outname + new_obj.fill_color = deepcopy(grb_obj.fill_color) + new_obj.outline_color = deepcopy(grb_obj.outline_color) + + new_obj.apertures = deepcopy(new_apertures) + + new_obj.solid_geometry = deepcopy(punched_solid_geometry) + new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None, + local_use=new_obj, use_thread=False) + + self.app.new_object('gerber', outname, init_func) + elif punch_method == 'prop': - pass + prop_factor = self.factor_entry.get_value() / 100.0 + + dia = None + + if isinstance(grb_obj.solid_geometry, list): + temp_solid_geometry = MultiPolygon(grb_obj.solid_geometry) + else: + temp_solid_geometry = grb_obj.solid_geometry + + punched_solid_geometry = temp_solid_geometry + + new_apertures = deepcopy(grb_obj.apertures) + new_apertures_items = new_apertures.items() + + # find maximum aperture id + new_apid = max([int(x) for x, __ in new_apertures_items]) + + # store here the clear geometry, the key is the new aperture size + holes_apertures = dict() + + for apid, apid_value in grb_obj.apertures.items(): + ap_type = apid_value['type'] + punching_geo = list() + + if ap_type == 'C' and self.circular_cb.get_value(): + dia = float(apid_value['size']) * prop_factor + for elem in apid_value['geometry']: + if 'follow' in elem and isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + + elif ap_type == 'O' and self.oblong_cb.get_value(): + width = float(apid_value['width']) + height = float(apid_value['height']) + + if width > height: + dia = float(apid_value['height']) * prop_factor + else: + dia = float(apid_value['width']) * prop_factor + + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + + 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 round(width, self.decimals) == round(height, self.decimals): + if self.square_cb.get_value(): + dia = float(apid_value['height']) * prop_factor + + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + elif self.rectangular_cb.get_value(): + if width > height: + dia = float(apid_value['height']) * prop_factor + else: + dia = float(apid_value['width']) * prop_factor + + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + + elif 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 + + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(dia / 2)) + + # if dia is None then none of the above applied so we skip the following + if dia is None: + continue + + punching_geo = MultiPolygon(punching_geo) + + if punching_geo is None or punching_geo.is_empty: + continue + + punched_solid_geometry = punched_solid_geometry.difference(punching_geo) + + # update the gerber apertures to include the clear geometry so it can be exported successfully + for elem in apid_value['geometry']: + # make it work only for Gerber Flashes who are Points in 'follow' + if 'solid' in elem and isinstance(elem['follow'], Point): + clear_apid_size = dia + for geo in punching_geo: + + # since there may be drills that do not drill into a pad we test only for geos in a pad + if geo.within(elem['solid']): + geo_elem = dict() + geo_elem['clear'] = geo.centroid + + if clear_apid_size not in holes_apertures: + holes_apertures[clear_apid_size] = dict() + holes_apertures[clear_apid_size]['type'] = 'C' + holes_apertures[clear_apid_size]['size'] = clear_apid_size + holes_apertures[clear_apid_size]['geometry'] = list() + + holes_apertures[clear_apid_size]['geometry'].append(deepcopy(geo_elem)) + + # add the clear geometry to new apertures; it's easier than to test if there are apertures with the same + # size and add there the clear geometry + for hole_size, ap_val in holes_apertures.items(): + new_apid += 1 + new_apertures[str(new_apid)] = deepcopy(ap_val) + + def init_func(new_obj, app_obj): + new_obj.options.update(grb_obj.options) + new_obj.options['name'] = outname + new_obj.fill_color = deepcopy(grb_obj.fill_color) + new_obj.outline_color = deepcopy(grb_obj.outline_color) + + new_obj.apertures = deepcopy(new_apertures) + + new_obj.solid_geometry = deepcopy(punched_solid_geometry) + new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None, + local_use=new_obj, use_thread=False) + + self.app.new_object('gerber', outname, init_func) def reset_fields(self): self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))