From 04b8de9fd32fafa235aebbfc55041a9f827ade4f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 18 May 2020 19:54:43 +0300 Subject: [PATCH] - working on a new Tool: Etch Compensation Tool -> installed the tool and created the GUI and class template --- AppGUI/MainGUI.py | 4 + AppTools/ToolCorners.py | 2 +- AppTools/ToolEtchCompensation.py | 304 +++++++++++++++++++++++++++++++ AppTools/__init__.py | 1 + App_Main.py | 5 + CHANGELOG.md | 1 + 6 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 AppTools/ToolEtchCompensation.py diff --git a/AppGUI/MainGUI.py b/AppGUI/MainGUI.py index ca0c9524..77f52c31 100644 --- a/AppGUI/MainGUI.py +++ b/AppGUI/MainGUI.py @@ -951,6 +951,8 @@ class MainGUI(QtWidgets.QMainWindow): 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")) + self.etch_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/etch_32.png'), _("Etch Compensation Tool")) # ######################################################################## # ########################## Excellon Editor Toolbar# #################### @@ -1913,6 +1915,8 @@ class MainGUI(QtWidgets.QMainWindow): 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")) + self.etch_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/etch_32.png'), _("Etch Compensation Tool")) # ######################################################################## # ## Excellon Editor Toolbar # ## diff --git a/AppTools/ToolCorners.py b/AppTools/ToolCorners.py index 8cd6cf79..8ac4dd91 100644 --- a/AppTools/ToolCorners.py +++ b/AppTools/ToolCorners.py @@ -52,7 +52,7 @@ class ToolCorners(AppTool): self.layout.addWidget(QtWidgets.QLabel('')) # Gerber object # - self.object_label = QtWidgets.QLabel('%s:' % _("Gerber Object")) + self.object_label = QtWidgets.QLabel('%s:' % _("GERBER")) self.object_label.setToolTip( _("The Gerber object that to which will be added corner markers.") ) diff --git a/AppTools/ToolEtchCompensation.py b/AppTools/ToolEtchCompensation.py new file mode 100644 index 00000000..370ba6c7 --- /dev/null +++ b/AppTools/ToolEtchCompensation.py @@ -0,0 +1,304 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# File Author: Marius Adrian Stanciu (c) # +# Date: 2/14/2020 # +# MIT Licence # +# ########################################################## + +from PyQt5 import QtWidgets, QtCore + +from AppTool import AppTool +from AppGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox + +from shapely.geometry import box + +from copy import deepcopy + +import logging +import gettext +import AppTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + +log = logging.getLogger('base') + + +class ToolEtchCompensation(AppTool): + + toolName = _("Etch Compensation Tool") + + def __init__(self, app): + self.app = app + self.decimals = self.app.decimals + + AppTool.__init__(self, app) + + self.tools_frame = QtWidgets.QFrame() + self.tools_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.tools_frame) + self.tools_box = QtWidgets.QVBoxLayout() + self.tools_box.setContentsMargins(0, 0, 0, 0) + self.tools_frame.setLayout(self.tools_box) + + # Title + title_label = QtWidgets.QLabel("%s" % self.toolName) + title_label.setStyleSheet(""" + QLabel + { + font-size: 16px; + font-weight: bold; + } + """) + self.tools_box.addWidget(title_label) + + # Grid Layout + grid0 = QtWidgets.QGridLayout() + grid0.setColumnStretch(0, 0) + grid0.setColumnStretch(1, 1) + self.tools_box.addLayout(grid0) + + grid0.addWidget(QtWidgets.QLabel(''), 0, 0, 1, 2) + + # Target Gerber Object + self.gerber_combo = FCComboBox() + self.gerber_combo.setModel(self.app.collection) + self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.gerber_combo.is_last = True + self.gerber_combo.obj_type = "Gerber" + + self.gerber_label = QtWidgets.QLabel('%s:' % _("GERBER")) + self.gerber_label.setToolTip( + _("Gerber object that will be inverted.") + ) + + grid0.addWidget(self.gerber_label, 1, 0, 1, 2) + grid0.addWidget(self.gerber_combo, 2, 0, 1, 2) + + grid0.addWidget(QtWidgets.QLabel(""), 3, 0, 1, 2) + + self.param_label = QtWidgets.QLabel("%s:" % _("Parameters")) + self.param_label.setToolTip('%s.' % _("Parameters for this tool")) + + grid0.addWidget(self.param_label, 4, 0, 1, 2) + + # Margin + self.margin_label = QtWidgets.QLabel('%s:' % _('Margin')) + self.margin_label.setToolTip( + _("Distance by which to avoid\n" + "the edges of the Gerber object.") + ) + self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.margin_entry.set_precision(self.decimals) + self.margin_entry.set_range(0.0000, 9999.9999) + self.margin_entry.setObjectName(_("Margin")) + + grid0.addWidget(self.margin_label, 5, 0, 1, 2) + grid0.addWidget(self.margin_entry, 6, 0, 1, 2) + + self.join_label = QtWidgets.QLabel('%s:' % _("Lines Join Style")) + self.join_label.setToolTip( + _("The way that the lines in the object outline will be joined.\n" + "Can be:\n" + "- rounded -> an arc is added between two joining lines\n" + "- square -> the lines meet in 90 degrees angle\n" + "- bevel -> the lines are joined by a third line") + ) + self.join_radio = RadioSet([ + {'label': 'Rounded', 'value': 'r'}, + {'label': 'Square', 'value': 's'}, + {'label': 'Bevel', 'value': 'b'} + ], orientation='vertical', stretch=False) + + grid0.addWidget(self.join_label, 7, 0, 1, 2) + grid0.addWidget(self.join_radio, 8, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 9, 0, 1, 2) + + self.invert_btn = FCButton(_('Invert Gerber')) + self.invert_btn.setToolTip( + _("Will invert the Gerber object: areas that have copper\n" + "will be empty of copper and previous empty area will be\n" + "filled with copper.") + ) + self.invert_btn.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + grid0.addWidget(self.invert_btn, 10, 0, 1, 2) + + self.tools_box.addStretch() + + # ## Reset Tool + self.reset_button = QtWidgets.QPushButton(_("Reset Tool")) + self.reset_button.setToolTip( + _("Will reset the tool parameters.") + ) + self.reset_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.tools_box.addWidget(self.reset_button) + + self.invert_btn.clicked.connect(self.on_grb_invert) + self.reset_button.clicked.connect(self.set_tool_ui) + + def install(self, icon=None, separator=None, **kwargs): + AppTool.install(self, icon, separator, shortcut='', **kwargs) + + def run(self, toggle=True): + self.app.defaults.report_usage("ToolInvertGerber()") + log.debug("ToolInvertGerber() is running ...") + + if toggle: + # if the splitter is hidden, display it, else hide it but only if the current widget is the same + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + else: + try: + if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName: + # if tab is populated with the tool but it does not have the focus, focus on it + if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab: + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) + else: + self.app.ui.splitter.setSizes([0, 1]) + except AttributeError: + pass + else: + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + AppTool.run(self) + self.set_tool_ui() + + self.app.ui.notebook.setTabText(2, _("Invert Tool")) + + def set_tool_ui(self): + self.margin_entry.set_value(float(self.app.defaults["tools_invert_margin"])) + self.join_radio.set_value(self.app.defaults["tools_invert_join_style"]) + + def on_grb_invert(self): + margin = self.margin_entry.get_value() + if round(margin, self.decimals) == 0.0: + margin = 1E-10 + + join_style = {'r': 1, 'b': 3, 's': 2}[self.join_radio.get_value()] + if join_style is None: + join_style = 'r' + + grb_circle_steps = int(self.app.defaults["gerber_circle_steps"]) + obj_name = self.gerber_combo.currentText() + + outname = obj_name + "_inverted" + + # Get source object. + try: + grb_obj = self.app.collection.get_by_name(obj_name) + except Exception as e: + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name))) + return "Could not retrieve object: %s with error: %s" % (obj_name, str(e)) + + if grb_obj is None: + if obj_name == '': + obj_name = 'None' + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name))) + return + + xmin, ymin, xmax, ymax = grb_obj.bounds() + + grb_box = box(xmin, ymin, xmax, ymax).buffer(margin, resolution=grb_circle_steps, join_style=join_style) + + try: + __ = iter(grb_obj.solid_geometry) + except TypeError: + grb_obj.solid_geometry = list(grb_obj.solid_geometry) + + new_solid_geometry = deepcopy(grb_box) + + for poly in grb_obj.solid_geometry: + new_solid_geometry = new_solid_geometry.difference(poly) + + new_options = {} + for opt in grb_obj.options: + new_options[opt] = deepcopy(grb_obj.options[opt]) + + new_apertures = {} + + # for apid, val in grb_obj.apertures.items(): + # new_apertures[apid] = {} + # for key in val: + # if key == 'geometry': + # new_apertures[apid]['geometry'] = [] + # for elem in val['geometry']: + # geo_elem = {} + # if 'follow' in elem: + # try: + # geo_elem['clear'] = elem['follow'].buffer(val['size'] / 2.0).exterior + # except AttributeError: + # # TODO should test if width or height is bigger + # geo_elem['clear'] = elem['follow'].buffer(val['width'] / 2.0).exterior + # if 'clear' in elem: + # if isinstance(elem['clear'], Polygon): + # try: + # geo_elem['solid'] = elem['clear'].buffer(val['size'] / 2.0, grb_circle_steps) + # except AttributeError: + # # TODO should test if width or height is bigger + # geo_elem['solid'] = elem['clear'].buffer(val['width'] / 2.0, grb_circle_steps) + # else: + # geo_elem['follow'] = elem['clear'] + # new_apertures[apid]['geometry'].append(deepcopy(geo_elem)) + # else: + # new_apertures[apid][key] = deepcopy(val[key]) + + if '0' not in new_apertures: + new_apertures['0'] = {} + new_apertures['0']['type'] = 'C' + new_apertures['0']['size'] = 0.0 + new_apertures['0']['geometry'] = [] + + try: + for poly in new_solid_geometry: + new_el = {} + new_el['solid'] = poly + new_el['follow'] = poly.exterior + new_apertures['0']['geometry'].append(new_el) + except TypeError: + new_el = {} + new_el['solid'] = new_solid_geometry + new_el['follow'] = new_solid_geometry.exterior + new_apertures['0']['geometry'].append(new_el) + + for td in new_apertures: + print(td, new_apertures[td]) + + def init_func(new_obj, app_obj): + new_obj.options.update(new_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(new_solid_geometry) + new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None, + local_use=new_obj, use_thread=False) + + self.app.app_obj.new_object('gerber', outname, init_func) + + def reset_fields(self): + self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + + @staticmethod + def poly2rings(poly): + return [poly.exterior] + [interior for interior in poly.interiors] +# end of file diff --git a/AppTools/__init__.py b/AppTools/__init__.py index 7a4ef9ca..5e3ae7c3 100644 --- a/AppTools/__init__.py +++ b/AppTools/__init__.py @@ -41,3 +41,4 @@ from AppTools.ToolPunchGerber import ToolPunchGerber from AppTools.ToolInvertGerber import ToolInvertGerber from AppTools.ToolCorners import ToolCorners +from AppTools.ToolEtchCompensation import ToolEtchCompensation \ No newline at end of file diff --git a/App_Main.py b/App_Main.py index 15c73f9f..359967b6 100644 --- a/App_Main.py +++ b/App_Main.py @@ -1360,6 +1360,7 @@ class App(QtCore.QObject): self.punch_tool = None self.invert_tool = None self.corners_tool = None + self.etch_tool = None # always install tools only after the shell is initialized because the self.inform.emit() depends on shell try: @@ -1948,6 +1949,9 @@ class App(QtCore.QObject): self.corners_tool = ToolCorners(self) self.corners_tool.install(icon=QtGui.QIcon(self.resource_location + '/corners_32.png'), pos=self.ui.menutool) + self.etch_tool = ToolEtchCompensation(self) + self.etch_tool.install(icon=QtGui.QIcon(self.resource_location + '/etcg_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) @@ -2096,6 +2100,7 @@ class App(QtCore.QObject): 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)) + self.ui.etch_btn.triggered.connect(lambda: self.etch_tool.run(toggle=True)) def object2editor(self): """ diff --git a/CHANGELOG.md b/CHANGELOG.md index 544e4fa5..41422b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ CHANGELOG for FlatCAM beta - more refactoring; solved some issues introduced by the refactoring - solved a circular import - updated the language translation files to the latest changes (no translation) +- working on a new Tool: Etch Compensation Tool -> installed the tool and created the GUI and class template 17.05.2020