diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 534efe50..cbd3e0c5 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1979,7 +1979,13 @@ class App(QtCore.QObject): self.film_tool.install(icon=QtGui.QIcon('share/film16.png')) self.paste_tool = SolderPaste(self) - self.paste_tool.install(icon=QtGui.QIcon('share/solderpastebis32.png'), separator=True) + self.paste_tool.install(icon=QtGui.QIcon('share/solderpastebis32.png')) + + self.silk_tool = ToolSilk(self) + self.silk_tool.install(icon=QtGui.QIcon('share/silk32.png'), separator=True) + + self.calculator_tool = ToolCalculator(self) + self.calculator_tool.install(icon=QtGui.QIcon('share/calculator24.png')) self.move_tool = ToolMove(self) self.move_tool.install(icon=QtGui.QIcon('share/move16.png'), pos=self.ui.menuedit, @@ -1997,9 +2003,6 @@ class App(QtCore.QObject): self.paint_tool.install(icon=QtGui.QIcon('share/paint16.png'), pos=self.ui.menutool, before=self.measurement_tool.menuAction, separator=True) - self.calculator_tool = ToolCalculator(self) - self.calculator_tool.install(icon=QtGui.QIcon('share/calculator24.png')) - self.transform_tool = ToolTransform(self) self.transform_tool.install(icon=QtGui.QIcon('share/transform.png'), pos=self.ui.menuoptions, separator=True) @@ -2083,6 +2086,7 @@ class App(QtCore.QObject): self.ui.panelize_btn.triggered.connect(lambda: self.panelize_tool.run(toggle=True)) self.ui.film_btn.triggered.connect(lambda: self.film_tool.run(toggle=True)) self.ui.solder_btn.triggered.connect(lambda: self.paste_tool.run(toggle=True)) + self.ui.silk_btn.triggered.connect(lambda: self.silk_tool.run(toggle=True)) self.ui.calculators_btn.triggered.connect(lambda: self.calculator_tool.run(toggle=True)) self.ui.transform_btn.triggered.connect(lambda: self.transform_tool.run(toggle=True)) diff --git a/README.md b/README.md index db0c9b64..81cfcc2f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ CAD program, and create G-Code for Isolation routing. - PDF import tool: working in making the PDF layer rendering multithreaded in itself (one layer rendered on each worker) - PDF import tool: solved a bug in parsing the rectangle subpath (an extra point was added to the subpath creating nonexisting geometry) - PDF import tool: finished layer rendering multithreading +- New tool: Silkscreen Tool: I am trying to remove the overlapped geo with the soldermask layer from overlay layer; layed out the class and functions - not working yet 23.04.2019 diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index 7362964f..cde708a0 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -638,6 +638,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.panelize_btn = self.toolbartools.addAction(QtGui.QIcon('share/panel16.png'), _("Panel Tool")) self.film_btn = self.toolbartools.addAction(QtGui.QIcon('share/film16.png'),_( "Film Tool")) self.solder_btn = self.toolbartools.addAction(QtGui.QIcon('share/solderpastebis32.png'), _("SolderPaste Tool")) + self.silk_btn = self.toolbartools.addAction(QtGui.QIcon('share/silk32.png'), _("Silkscreen Tool")) + self.toolbartools.addSeparator() self.calculators_btn = self.toolbartools.addAction(QtGui.QIcon('share/calculator24.png'), _("Calculators Tool")) @@ -1843,6 +1845,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.film_btn = self.toolbartools.addAction(QtGui.QIcon('share/film16.png'), _("Film Tool")) self.solder_btn = self.toolbartools.addAction(QtGui.QIcon('share/solderpastebis32.png'), _("SolderPaste Tool")) + self.silk_btn = self.toolbartools.addAction(QtGui.QIcon('share/silk32.png'), _("Silkscreen Tool")) + self.toolbartools.addSeparator() self.calculators_btn = self.toolbartools.addAction(QtGui.QIcon('share/calculator24.png'), @@ -2110,6 +2114,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.dblsidedtool.run(toggle=True) return + # Silkscreen Tool + if key == QtCore.Qt.Key_E: + self.app.silk_tool.run(toggle=True) + return + # Solder Paste Dispensing Tool if key == QtCore.Qt.Key_K: self.app.paste_tool.run(toggle=True) diff --git a/flatcamTools/ToolPDF.py b/flatcamTools/ToolPDF.py index 39d7d987..9e3f0dfb 100644 --- a/flatcamTools/ToolPDF.py +++ b/flatcamTools/ToolPDF.py @@ -2,7 +2,7 @@ # FlatCAM: 2D Post-processing for Manufacturing # # http://flatcam.org # # File Author: Marius Adrian Stanciu (c) # -# Date: 3/10/2019 # +# Date: 4/23/2019 # # MIT Licence # ############################################################ diff --git a/flatcamTools/ToolSilk.py b/flatcamTools/ToolSilk.py new file mode 100644 index 00000000..7a92089f --- /dev/null +++ b/flatcamTools/ToolSilk.py @@ -0,0 +1,295 @@ +############################################################ +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# File Author: Marius Adrian Stanciu (c) # +# Date: 4/24/2019 # +# MIT Licence # +############################################################ + + +from FlatCAMTool import FlatCAMTool +# from copy import copy, deepcopy +from ObjectCollection import * +# import time + +import gettext +import FlatCAMTranslation as fcTranslate +from shapely.geometry import base +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class ToolSilk(FlatCAMTool): + + toolName = _("Silkscreen Tool") + + def __init__(self, app): + self.app = app + + FlatCAMTool.__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) + + # Form Layout + form_layout = QtWidgets.QFormLayout() + self.tools_box.addLayout(form_layout) + + # Object Silkscreen + self.silk_object_combo = QtWidgets.QComboBox() + self.silk_object_combo.setModel(self.app.collection) + self.silk_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.silk_object_combo.setCurrentIndex(1) + + self.silk_object_label = QtWidgets.QLabel("Silk Gerber:") + self.silk_object_label.setToolTip( + _("Silkscreen Gerber object to be adjusted\n" + "so it does not intersects the soldermask.") + ) + e_lab_0 = QtWidgets.QLabel('') + + form_layout.addRow(self.silk_object_label, self.silk_object_combo) + + # Object Soldermask + self.sm_object_combo = QtWidgets.QComboBox() + self.sm_object_combo.setModel(self.app.collection) + self.sm_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.sm_object_combo.setCurrentIndex(1) + + self.sm_object_label = QtWidgets.QLabel("SM Gerber:") + self.sm_object_label.setToolTip( + _("Soldermask Gerber object that will adjust\n" + "the silkscreen so it does not overlap.") + ) + e_lab_1 = QtWidgets.QLabel('') + + form_layout.addRow(self.sm_object_label, self.sm_object_combo) + form_layout.addRow(e_lab_1) + + self.intersect_btn = FCButton(_('Remove overlap')) + self.intersect_btn.setToolTip( + _("Remove the silkscreen geometry\n" + "that overlaps over the soldermask.") + ) + self.tools_box.addWidget(self.intersect_btn) + + self.tools_box.addStretch() + + # QTimer for periodic check + self.check_thread = QtCore.QTimer() + # Every time an intersection job is started we add a promise; every time an intersection job is finished + # we remove a promise. + # When empty we start the layer rendering + self.promises = [] + + self.new_apertures = {} + self.new_solid_geometry = [] + + self.solder_union = None + + self.intersect_btn.clicked.connect(self.on_intersection_click) + + def install(self, icon=None, separator=None, **kwargs): + FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs) + + def run(self, toggle=True): + self.app.report_usage("ToolNonCopperClear()") + + 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: + 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.new_apertures.clear() + self.new_solid_geometry = [] + + self.app.ui.notebook.setTabText(2, _("Silk Tool")) + + def set_tool_ui(self): + self.tools_frame.show() + + def on_intersection_click(self): + self.silk_obj_name = self.silk_object_combo.currentText() + # Get source object. + try: + self.silk_obj = self.app.collection.get_by_name(self.silk_obj_name) + except: + self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name) + return "Could not retrieve object: %s" % self.silk_obj_name + + self.sm_obj_name = self.silk_object_combo.currentText() + # Get source object. + try: + self.sm_obj = self.app.collection.get_by_name(self.sm_obj_name) + except: + self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name) + return "Could not retrieve object: %s" % self.sm_obj_name + + # crate the new_apertures dict structure + for apid in self.silk_obj.apertures: + self.new_apertures[apid] = {} + self.new_apertures[apid]['type'] = 'C' + self.new_apertures[apid]['size'] = self.silk_obj.apertures[apid]['size'] + self.new_apertures[apid]['solid_geometry'] = [] + + geo_union_list = [] + for apid1 in self.sm_obj.apertures: + geo_union_list += self.sm_obj.apertures[apid1]['solid_geometry'] + self.solder_union = cascaded_union(geo_union_list) + + # start the QTimer with 1 second period check + self.periodic_check(1000) + for apid in self.silk_obj.apertures: + ap_size = self.silk_obj.apertures[apid]['size'] + geo_list = self.silk_obj.apertures[apid]['solid_geometry'] + self.app.worker_task.emit({'fcn': self.aperture_intersection, + 'params': [apid, ap_size, geo_list]}) + + def aperture_intersection(self, aperture_id, aperture_size, geo_list): + self.promises.append(aperture_id) + new_solid_geometry = [] + + with self.app.proc_container.new(_("Parsing aperture %s geometry ..." % str(aperture_id))): + for geo_silk in geo_list: + for sm_ap in self.sm_obj.apertures: + for key in self.sm_obj.apertures[sm_ap]: + if key == 'solid_geometry': + if geo_silk.intersects(self.solder_union): + new_geo = geo_silk.symmetric_difference(self.solder_union) + # if the resulting geometry is not empty add it to the new_apertures solid_geometry + if type(new_geo) == MultiPolygon: + for g in new_geo: + new_solid_geometry.append(g) + else: + new_solid_geometry.append(new_geo) + + else: + new_solid_geometry.append(geo_silk) + + # while not self.new_apertures[aperture_id]['solid_geometry']: + try: + self.new_apertures[aperture_id]['solid_geometry'] = new_solid_geometry + except: + pass + + # while aperture_id in self.promises: + # removal from list is done in a multithreaded way therefore not always the removal can be done + try: + self.promises.remove(aperture_id) + except: + pass + + def periodic_check(self, check_period): + """ + This function starts an QTimer and it will periodically check if intersections are done + + :param check_period: time at which to check periodically + :return: + """ + + log.debug("ToolSilk --> Periodic Check started.") + + try: + self.check_thread.stop() + except: + pass + + self.check_thread.setInterval(check_period) + try: + self.check_thread.timeout.disconnect(self.periodic_check_handler) + except: + pass + + self.check_thread.timeout.connect(self.periodic_check_handler) + self.check_thread.start(QtCore.QThread.HighPriority) + + def periodic_check_handler(self): + """ + If the intersections workers finished then start creating the solid_geometry + :return: + """ + # log.debug("checking parsing --> %s" % str(self.parsing_promises)) + + outname = self.silk_object_combo.currentText() + '_cleaned' + + try: + if not self.promises: + self.check_thread.stop() + # intersection jobs finished, start the creation of solid_geometry + self.app.worker_task.emit({'fcn': self.new_silkscreen_object, + 'params': [outname]}) + + log.debug("ToolPDF --> Periodic check finished.") + except Exception: + traceback.print_exc() + + def new_silkscreen_object(self, outname): + + def obj_init(grb_obj, app_obj): + + grb_obj.apertures = deepcopy(self.new_apertures) + + poly_buff = [] + for ap in self.new_apertures: + for k in self.new_apertures[ap]: + if k == 'solid_geometry': + poly_buff += self.new_apertures[ap][k] + + poly_buff = unary_union(poly_buff) + try: + poly_buff = poly_buff.buffer(0.0000001) + except ValueError: + pass + try: + poly_buff = poly_buff.buffer(-0.0000001) + except ValueError: + pass + + grb_obj.solid_geometry = deepcopy(poly_buff) + # self.new_apertures.clear() + + with self.app.proc_container.new(_("Generating cleaned SS object ...")): + ret = self.app.new_object('gerber', outname, obj_init, autoselected=False) + if ret == 'fail': + self.app.inform.emit(_('[ERROR_NOTCL] Generating SilkScreen file failed.')) + return + # Register recent file + self.app.file_opened.emit('gerber', outname) + # GUI feedback + self.app.inform.emit(_("[success] Created: %s") % outname) + + def reset_fields(self): + self.silk_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.sm_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + + diff --git a/flatcamTools/__init__.py b/flatcamTools/__init__.py index 71286c55..abbbee0e 100644 --- a/flatcamTools/__init__.py +++ b/flatcamTools/__init__.py @@ -16,5 +16,7 @@ from flatcamTools.ToolTransform import ToolTransform from flatcamTools.ToolSolderPaste import SolderPaste from flatcamTools.ToolPcbWizard import PcbWizard from flatcamTools.ToolPDF import ToolPDF +from flatcamTools.ToolSilk import ToolSilk + from flatcamTools.ToolShell import FCShell diff --git a/share/silk32.png b/share/silk32.png new file mode 100644 index 00000000..529c2674 Binary files /dev/null and b/share/silk32.png differ