diff --git a/CHANGELOG.md b/CHANGELOG.md index 57dc65db..2a2f5326 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM beta ================================================= +17.05.2020 + +- added new FlatCAM Tool: Corner Markers Tool which will add line markers in the selected corners of the bounding box of the targeted Gerber object + 16.05.2020 - worked on the NCC Tool; added a new clear method named 'Combo' which will go through all methods until the clear is done diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 6f639f8e..906dcb8f 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1387,6 +1387,7 @@ class App(QtCore.QObject): self.align_objects_tool = None self.punch_tool = None self.invert_tool = None + self.corners_tool = None # always install tools only after the shell is initialized because the self.inform.emit() depends on shell try: @@ -1975,6 +1976,9 @@ class App(QtCore.QObject): self.invert_tool = ToolInvertGerber(self) self.invert_tool.install(icon=QtGui.QIcon(self.resource_location + '/invert32.png'), pos=self.ui.menutool) + self.corners_tool = ToolCorners(self) + self.corners_tool.install(icon=QtGui.QIcon(self.resource_location + '/corners_32.png'), pos=self.ui.menutool) + self.transform_tool = ToolTransform(self) self.transform_tool.install(icon=QtGui.QIcon(self.resource_location + '/transform.png'), pos=self.ui.menuoptions, separator=True) @@ -2125,6 +2129,7 @@ class App(QtCore.QObject): self.ui.fiducials_btn.triggered.connect(lambda: self.fiducial_tool.run(toggle=True)) self.ui.punch_btn.triggered.connect(lambda: self.punch_tool.run(toggle=True)) self.ui.invert_btn.triggered.connect(lambda: self.invert_tool.run(toggle=True)) + self.ui.corners_tool_btn.triggered.connect(lambda: self.corners_tool.run(toggle=True)) def object2editor(self): """ diff --git a/assets/resources/corners_32.png b/assets/resources/corners_32.png new file mode 100644 index 00000000..b9f87261 Binary files /dev/null and b/assets/resources/corners_32.png differ diff --git a/assets/resources/dark_resources/corners_32.png b/assets/resources/dark_resources/corners_32.png new file mode 100644 index 00000000..c5ad0f00 Binary files /dev/null and b/assets/resources/dark_resources/corners_32.png differ diff --git a/defaults.py b/defaults.py index 64f0fa70..d8bd7186 100644 --- a/defaults.py +++ b/defaults.py @@ -525,6 +525,12 @@ class FlatCAMDefaults: # Distance Tool "tools_dist_snap_center": False, + # Corner Markers Tool + + "tools_corners_thickness": 0.1, + "tools_corners_length": 3.0, + "tools_corners_margin": 0.0, + # ######################################################################################################## # ################################ TOOLS 2 ############################################################### # ######################################################################################################## diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index daf20109..0d171f95 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -954,6 +954,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/punch32.png'), _("Punch Gerber Tool")) self.invert_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/invert32.png'), _("Invert Gerber Tool")) + self.corners_tool_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/corners_32.png'), _("Corner Markers Tool")) # ######################################################################## # ########################## Excellon Editor Toolbar# #################### @@ -1612,6 +1614,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): Alt+L  %s + + Alt+M +  %s + Alt+N  %s @@ -1742,7 +1748,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): _("Align Objects Tool"), _("Calculators Tool"), _("2-Sided PCB Tool"), _("Transformations Tool"), _("Punch Gerber Tool"), _("Extract Drills Tool"), _("Fiducials Tool"), _("Solder Paste Dispensing Tool"), - _("Film PCB Tool"), _("Non-Copper Clearing Tool"), _("Optimal Tool"), + _("Film PCB Tool"), _("Corner Markers Tool"), _("Non-Copper Clearing Tool"), _("Optimal Tool"), _("Paint Area Tool"), _("QRCode Tool"), _("Rules Check Tool"), _("View File Source"), _("Cutout PCB Tool"), _("Enable all Plots"), _("Disable all Plots"), _("Disable Non-selected Plots"), @@ -2633,6 +2639,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/punch32.png'), _("Punch Gerber Tool")) self.invert_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/invert32.png'), _("Invert Gerber Tool")) + self.corners_tool_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/corners_32.png'), _("Corner Markers Tool")) # ######################################################################## # ## Excellon Editor Toolbar # ## @@ -3022,6 +3030,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.film_tool.run(toggle=True) return + # Corner Markers Tool + if key == QtCore.Qt.Key_M: + self.app.corners_tool.run(toggle=True) + return + # Non-Copper Clear Tool if key == QtCore.Qt.Key_N: self.app.ncclear_tool.run(toggle=True) diff --git a/flatcamGUI/preferences/PreferencesUIManager.py b/flatcamGUI/preferences/PreferencesUIManager.py index e7fe1347..a9380243 100644 --- a/flatcamGUI/preferences/PreferencesUIManager.py +++ b/flatcamGUI/preferences/PreferencesUIManager.py @@ -467,6 +467,12 @@ class PreferencesUIManager: "tools_solderpaste_pp": self.ui.tools_defaults_form.tools_solderpaste_group.pp_combo, "tools_sub_close_paths": self.ui.tools_defaults_form.tools_sub_group.close_paths_cb, + # Corner Markers Tool + + "tools_corners_thickness": self.ui.tools_defaults_form.tools_corners_group.thick_entry, + "tools_corners_length": self.ui.tools_defaults_form.tools_corners_group.l_entry, + "tools_corners_margin": self.ui.tools_defaults_form.tools_corners_group.margin_entry, + # ####################################################################################################### # ########################################## TOOLS 2 #################################################### # ####################################################################################################### diff --git a/flatcamGUI/preferences/tools/ToolsCornersPrefGroupUI.py b/flatcamGUI/preferences/tools/ToolsCornersPrefGroupUI.py new file mode 100644 index 00000000..738b9580 --- /dev/null +++ b/flatcamGUI/preferences/tools/ToolsCornersPrefGroupUI.py @@ -0,0 +1,81 @@ +from PyQt5 import QtWidgets +from PyQt5.QtCore import QSettings + +from flatcamGUI.GUIElements import FCDoubleSpinner +from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + +settings = QSettings("Open Source", "FlatCAM") +if settings.contains("machinist"): + machinist_setting = settings.value('machinist', type=int) +else: + machinist_setting = 0 + + +class ToolsCornersPrefGroupUI(OptionsGroupUI): + def __init__(self, decimals=4, parent=None): + # OptionsGroupUI.__init__(self, "Calculators Tool Options", parent=parent) + super(ToolsCornersPrefGroupUI, self).__init__(self, parent=parent) + + self.setTitle(str(_("Corner Markers Options"))) + self.decimals = decimals + + grid0 = QtWidgets.QGridLayout() + grid0.setColumnStretch(0, 0) + grid0.setColumnStretch(1, 1) + self.layout.addLayout(grid0) + + self.param_label = QtWidgets.QLabel('%s:' % _('Parameters')) + self.param_label.setToolTip( + _("Parameters used for this tool.") + ) + grid0.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() + 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) + + grid0.addWidget(self.thick_label, 1, 0) + grid0.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() + self.l_entry.set_range(-9999.9999, 9999.9999) + self.l_entry.set_precision(self.decimals) + self.l_entry.setSingleStep(10 ** -self.decimals) + + # Margin # + self.margin_label = QtWidgets.QLabel('%s:' % _("Margin")) + self.margin_label.setToolTip( + _("Bounding box margin.") + ) + self.margin_entry = FCDoubleSpinner() + self.margin_entry.set_range(-9999.9999, 9999.9999) + self.margin_entry.set_precision(self.decimals) + self.margin_entry.setSingleStep(0.1) + + grid0.addWidget(self.margin_label, 2, 0) + grid0.addWidget(self.margin_entry, 2, 1) + + grid0.addWidget(self.l_label, 4, 0) + grid0.addWidget(self.l_entry, 4, 1) + + self.layout.addStretch() diff --git a/flatcamGUI/preferences/tools/ToolsPreferencesUI.py b/flatcamGUI/preferences/tools/ToolsPreferencesUI.py index dc3061a8..7a58dff2 100644 --- a/flatcamGUI/preferences/tools/ToolsPreferencesUI.py +++ b/flatcamGUI/preferences/tools/ToolsPreferencesUI.py @@ -3,6 +3,7 @@ from PyQt5.QtCore import QSettings from flatcamGUI.preferences.tools.ToolsSubPrefGroupUI import ToolsSubPrefGroupUI from flatcamGUI.preferences.tools.ToolsSolderpastePrefGroupUI import ToolsSolderpastePrefGroupUI +from flatcamGUI.preferences.tools.ToolsCornersPrefGroupUI import ToolsCornersPrefGroupUI from flatcamGUI.preferences.tools.ToolsTransformPrefGroupUI import ToolsTransformPrefGroupUI from flatcamGUI.preferences.tools.ToolsCalculatorsPrefGroupUI import ToolsCalculatorsPrefGroupUI from flatcamGUI.preferences.tools.ToolsPanelizePrefGroupUI import ToolsPanelizePrefGroupUI @@ -62,6 +63,9 @@ class ToolsPreferencesUI(QtWidgets.QWidget): self.tools_solderpaste_group = ToolsSolderpastePrefGroupUI(decimals=self.decimals) self.tools_solderpaste_group.setMinimumWidth(200) + self.tools_corners_group = ToolsCornersPrefGroupUI(decimals=self.decimals) + self.tools_corners_group.setMinimumWidth(200) + self.tools_sub_group = ToolsSubPrefGroupUI(decimals=self.decimals) self.tools_sub_group.setMinimumWidth(200) @@ -84,6 +88,7 @@ class ToolsPreferencesUI(QtWidgets.QWidget): self.vlay4 = QtWidgets.QVBoxLayout() self.vlay4.addWidget(self.tools_solderpaste_group) + self.vlay4.addWidget(self.tools_corners_group) self.layout.addLayout(self.vlay) self.layout.addLayout(self.vlay1) diff --git a/flatcamTools/ToolCorners.py b/flatcamTools/ToolCorners.py new file mode 100644 index 00000000..4abf1587 --- /dev/null +++ b/flatcamTools/ToolCorners.py @@ -0,0 +1,423 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# File Author: Marius Adrian Stanciu (c) # +# Date: 5/17/2020 # +# MIT Licence # +# ########################################################## + +from PyQt5 import QtWidgets, QtCore + +from FlatCAMTool import FlatCAMTool +from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, FCButton + +from shapely.geometry import MultiPolygon, LineString + +from copy import deepcopy +import logging + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + +log = logging.getLogger('base') + + +class ToolCorners(FlatCAMTool): + + toolName = _("Corner Markers Tool") + + def __init__(self, app): + FlatCAMTool.__init__(self, app) + + self.app = app + self.canvas = self.app.plotcanvas + + 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('%s:' % _("Gerber Object")) + self.object_label.setToolTip( + _("The Gerber object that 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('%s:' % _('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) + + # ## 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) + + # 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.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 + + # store the flattened geometry here: + self.flat_geometry = [] + + # Tool properties + self.fid_dia = None + + self.grb_steps_per_circle = self.app.defaults["gerber_circle_steps"] + + # SIGNALS + self.add_marker_button.clicked.connect(self.add_markers) + + def run(self, toggle=True): + self.app.defaults.report_usage("ToolCorners()") + + 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]) + + FlatCAMTool.run(self) + + self.set_tool_ui() + + self.app.ui.notebook.setTabText(2, _("Corners Tool")) + + def install(self, icon=None, separator=None, **kwargs): + FlatCAMTool.install(self, icon, separator, shortcut='Alt+M', **kwargs) + + 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"])) + + 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() + + # 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()) + + try: + self.grb_object = model_index.internalPointer().obj + except Exception as e: + log.debug("ToolCorners.add_markers() --> %s" % str(e)) + self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ...")) + return + + xmin, ymin, xmax, ymax = self.grb_object.bounds() + points = {} + if tl_state: + points['tl'] = (xmin, ymax) + if tr_state: + points['tr'] = (xmax, ymax) + if bl_state: + points['bl'] = (xmin, ymin) + if br_state: + points['br'] = (xmax, ymin) + + self.add_corners_geo(points, g_obj=self.grb_object) + + 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.on_exit() + + def add_corners_geo(self, points_storage, g_obj): + """ + Add geometry to the solid_geometry of the copper Gerber object + + :param points_storage: a dictionary holding the points where to add corners + :param g_obj: the Gerber object where to add the geometry + :return: None + """ + + line_thickness = self.thick_entry.get_value() + line_length = self.l_entry.get_value() + margin = self.margin_entry.get_value() + + geo_list = [] + + if not points_storage: + self.app.inform.emit("[ERROR_NOTCL] %s." % _("Please select at least a location")) + return + + for key in points_storage: + if key == 'tl': + pt = points_storage[key] + x = pt[0] - margin - line_thickness / 2.0 + y = pt[1] + margin + line_thickness / 2.0 + line_geo_hor = LineString([ + (x, y), (x + line_length, y) + ]) + line_geo_vert = LineString([ + (x, y), (x, y - line_length) + ]) + geo_list.append(line_geo_hor) + geo_list.append(line_geo_vert) + if key == 'tr': + pt = points_storage[key] + x = pt[0] + margin + line_thickness / 2.0 + y = pt[1] + margin + line_thickness / 2.0 + line_geo_hor = LineString([ + (x, y), (x - line_length, y) + ]) + line_geo_vert = LineString([ + (x, y), (x, y - line_length) + ]) + geo_list.append(line_geo_hor) + geo_list.append(line_geo_vert) + if key == 'bl': + pt = points_storage[key] + x = pt[0] - margin - line_thickness / 2.0 + y = pt[1] - margin - line_thickness / 2.0 + line_geo_hor = LineString([ + (x, y), (x + line_length, y) + ]) + line_geo_vert = LineString([ + (x, y), (x, y + line_length) + ]) + geo_list.append(line_geo_hor) + geo_list.append(line_geo_vert) + if key == 'br': + pt = points_storage[key] + x = pt[0] + margin + line_thickness / 2.0 + y = pt[1] - margin - line_thickness / 2.0 + line_geo_hor = LineString([ + (x, y), (x - line_length, y) + ]) + line_geo_vert = LineString([ + (x, y), (x, y + line_length) + ]) + geo_list.append(line_geo_hor) + geo_list.append(line_geo_vert) + + aperture_found = None + for ap_id, ap_val in g_obj.apertures.items(): + if ap_val['type'] == 'C' and ap_val['size'] == line_thickness: + aperture_found = ap_id + break + + geo_buff_list = [] + if aperture_found: + for geo in geo_list: + geo_buff = geo.buffer(line_thickness / 2.0, resolution=self.grb_steps_per_circle, join_style=2) + geo_buff_list.append(geo_buff) + + dict_el = {} + dict_el['follow'] = geo + dict_el['solid'] = geo_buff + g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el)) + else: + ap_keys = list(g_obj.apertures.keys()) + if ap_keys: + new_apid = str(int(max(ap_keys)) + 1) + else: + new_apid = '10' + + g_obj.apertures[new_apid] = {} + g_obj.apertures[new_apid]['type'] = 'C' + g_obj.apertures[new_apid]['size'] = line_thickness + g_obj.apertures[new_apid]['geometry'] = [] + + for geo in geo_list: + geo_buff = geo.buffer(line_thickness / 2.0, resolution=self.grb_steps_per_circle, join_style=2) + geo_buff_list.append(geo_buff) + + dict_el = {} + dict_el['follow'] = geo + dict_el['solid'] = geo_buff + g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el)) + + s_list = [] + if g_obj.solid_geometry: + try: + for poly in g_obj.solid_geometry: + s_list.append(poly) + except TypeError: + s_list.append(g_obj.solid_geometry) + + geo_buff_list = MultiPolygon(geo_buff_list) + geo_buff_list = geo_buff_list.buffer(0) + for poly in geo_buff_list: + s_list.append(poly) + g_obj.solid_geometry = MultiPolygon(s_list) + + def replot(self, obj, run_thread=True): + def worker_task(): + with self.app.proc_container.new('%s...' % _("Plotting")): + obj.plot() + + if run_thread: + self.app.worker_task.emit({'fcn': worker_task, 'params': []}) + else: + worker_task() + + def on_exit(self): + # plot the object + try: + self.replot(obj=self.grb_object) + except (AttributeError, TypeError): + return + + # 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("ToolCorners.on_exit() copper_obj bounds error --> %s" % str(e)) + + # reset the variables + self.grb_object = None + + self.app.call_source = "app" + self.app.inform.emit('[success] %s' % _("Corners Tool exit.")) diff --git a/flatcamTools/ToolFiducials.py b/flatcamTools/ToolFiducials.py index 1b3513d4..b183914d 100644 --- a/flatcamTools/ToolFiducials.py +++ b/flatcamTools/ToolFiducials.py @@ -234,7 +234,7 @@ class ToolFiducials(FlatCAMTool): # Line Thickness # self.line_thickness_label = QtWidgets.QLabel('%s:' % _("Line thickness")) self.line_thickness_label.setToolTip( - _("Bounding box margin.") + _("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) @@ -353,7 +353,8 @@ class ToolFiducials(FlatCAMTool): self.margin_val = None self.sec_position = None - self.geo_steps_per_circle = 128 + + self.grb_steps_per_circle = self.app.defaults["gerber_circle_steps"] self.click_points = [] @@ -471,7 +472,7 @@ class ToolFiducials(FlatCAMTool): if self.mode_method == 'auto': xmin, ymin, xmax, ymax = self.grb_object.bounds() bbox = box(xmin, ymin, xmax, ymax) - buf_bbox = bbox.buffer(self.margin_val, join_style=2) + buf_bbox = bbox.buffer(self.margin_val, self.grb_steps_per_circle, join_style=2) x0, y0, x1, y1 = buf_bbox.bounds self.click_points.append( @@ -539,7 +540,7 @@ class ToolFiducials(FlatCAMTool): radius = fid_size / 2.0 if fid_type == 'circular': - geo_list = [Point(pt).buffer(radius) for pt in points_list] + geo_list = [Point(pt).buffer(radius, self.grb_steps_per_circle) for pt in points_list] aperture_found = None for ap_id, ap_val in g_obj.apertures.items(): @@ -604,8 +605,8 @@ class ToolFiducials(FlatCAMTool): geo_buff_list = [] if aperture_found: for geo in geo_list: - geo_buff_h = geo[0].buffer(line_thickness / 2.0) - geo_buff_v = geo[1].buffer(line_thickness / 2.0) + geo_buff_h = geo[0].buffer(line_thickness / 2.0, self.grb_steps_per_circle) + geo_buff_v = geo[1].buffer(line_thickness / 2.0, self.grb_steps_per_circle) geo_buff_list.append(geo_buff_h) geo_buff_list.append(geo_buff_v) @@ -629,8 +630,8 @@ class ToolFiducials(FlatCAMTool): g_obj.apertures[new_apid]['geometry'] = [] for geo in geo_list: - geo_buff_h = geo[0].buffer(line_thickness / 2.0) - geo_buff_v = geo[1].buffer(line_thickness / 2.0) + geo_buff_h = geo[0].buffer(line_thickness / 2.0, self.grb_steps_per_circle) + geo_buff_v = geo[1].buffer(line_thickness / 2.0, self.grb_steps_per_circle) geo_buff_list.append(geo_buff_h) geo_buff_list.append(geo_buff_v) diff --git a/flatcamTools/ToolNCC.py b/flatcamTools/ToolNCC.py index 1d83d966..5ff643aa 100644 --- a/flatcamTools/ToolNCC.py +++ b/flatcamTools/ToolNCC.py @@ -2093,75 +2093,77 @@ class NonCopperClear(FlatCAMTool, Gerber): isolated_geo = self.generate_envelope(tool_iso / 2, 0) if isolated_geo == 'fail': - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) - else: - if ncc_margin < tool_iso: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Isolation geometry is broken. Margin is less " - "than isolation tool diameter.")) - try: - for geo_elem in isolated_geo: - # provide the app with a way to process the GUI events when in a blocking loop - QtWidgets.QApplication.processEvents() + self.app.inform.emit('[ERROR_NOTCL] %s %s' % + (_("Isolation geometry could not be generated."), str(tool_iso))) + continue - if self.app.abort_flag: - # graceful abort requested by the user - raise grace + if ncc_margin < tool_iso: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Isolation geometry is broken. Margin is less " + "than isolation tool diameter.")) + try: + for geo_elem in isolated_geo: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() - if isinstance(geo_elem, Polygon): - for ring in self.poly2rings(geo_elem): + if self.app.abort_flag: + # graceful abort requested by the user + raise grace + + if isinstance(geo_elem, Polygon): + for ring in self.poly2rings(geo_elem): + new_geo = ring.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiPolygon): + for poly in geo_elem: + for ring in self.poly2rings(poly): new_geo = ring.intersection(bounding_box) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) - elif isinstance(geo_elem, MultiPolygon): - for poly in geo_elem: - for ring in self.poly2rings(poly): - new_geo = ring.intersection(bounding_box) - if new_geo and not new_geo.is_empty: - new_geometry.append(new_geo) - elif isinstance(geo_elem, LineString): - new_geo = geo_elem.intersection(bounding_box) - if new_geo: - if not new_geo.is_empty: - new_geometry.append(new_geo) - elif isinstance(geo_elem, MultiLineString): - for line_elem in geo_elem: - new_geo = line_elem.intersection(bounding_box) - if new_geo and not new_geo.is_empty: - new_geometry.append(new_geo) - except TypeError: - if isinstance(isolated_geo, Polygon): - for ring in self.poly2rings(isolated_geo): - new_geo = ring.intersection(bounding_box) - if new_geo: - if not new_geo.is_empty: - new_geometry.append(new_geo) - elif isinstance(isolated_geo, LineString): - new_geo = isolated_geo.intersection(bounding_box) - if new_geo and not new_geo.is_empty: - new_geometry.append(new_geo) - elif isinstance(isolated_geo, MultiLineString): - for line_elem in isolated_geo: + elif isinstance(geo_elem, LineString): + new_geo = geo_elem.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiLineString): + for line_elem in geo_elem: new_geo = line_elem.intersection(bounding_box) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) + except TypeError: + if isinstance(isolated_geo, Polygon): + for ring in self.poly2rings(isolated_geo): + new_geo = ring.intersection(bounding_box) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, LineString): + new_geo = isolated_geo.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(isolated_geo, MultiLineString): + for line_elem in isolated_geo: + new_geo = line_elem.intersection(bounding_box) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) - # a MultiLineString geometry element will show that the isolation is broken for this tool - for geo_e in new_geometry: - if type(geo_e) == MultiLineString: - warning_flag += 1 - break + # a MultiLineString geometry element will show that the isolation is broken for this tool + for geo_e in new_geometry: + if type(geo_e) == MultiLineString: + warning_flag += 1 + break - current_uid = 0 - for k, v in tools_storage.items(): - if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, - tool_iso)): - current_uid = int(k) - # add the solid_geometry to the current too in self.paint_tools dictionary - # and then reset the temporary list that stored that solid_geometry - v['solid_geometry'] = deepcopy(new_geometry) - v['data']['name'] = name - break - geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + current_uid = 0 + for k, v in tools_storage.items(): + if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, + tool_iso)): + current_uid = int(k) + # add the solid_geometry to the current too in self.paint_tools dictionary + # and then reset the temporary list that stored that solid_geometry + v['solid_geometry'] = deepcopy(new_geometry) + v['data']['name'] = name + break + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) sol_geo = cascaded_union(isolated_geo) if has_offset is True: diff --git a/flatcamTools/__init__.py b/flatcamTools/__init__.py index bdd7e58b..184a7f43 100644 --- a/flatcamTools/__init__.py +++ b/flatcamTools/__init__.py @@ -1,4 +1,3 @@ -import sys from flatcamTools.ToolCalculators import ToolCalculator from flatcamTools.ToolCalibration import ToolCalibration @@ -41,3 +40,4 @@ from flatcamTools.ToolTransform import ToolTransform from flatcamTools.ToolPunchGerber import ToolPunchGerber from flatcamTools.ToolInvertGerber import ToolInvertGerber +from flatcamTools.ToolCorners import ToolCorners