Sfoglia il codice sorgente

- fixed issues in the Tool Subtract
- remade Tool Subtract to use multiprocessing when processing geometry
- the resulting Gerber file from Tool Subtract has now the attribute source_file populated

Marius Stanciu 5 anni fa
parent
commit
8d16bebf44
3 ha cambiato i file con 140 aggiunte e 138 eliminazioni
  1. 7 7
      AppTools/ToolRulesCheck.py
  2. 130 131
      AppTools/ToolSub.py
  3. 3 0
      CHANGELOG.md

+ 7 - 7
AppTools/ToolRulesCheck.py

@@ -951,7 +951,7 @@ class RulesCheck(AppTool):
                                 geo = geo_el['solid']
                                 pt = geo.representative_point()
                                 points_list.append((pt.x, pt.y))
-                except Exception as e:
+                except Exception:
                     # An exception  will be raised for the 'size' key in case of apertures of type AM (macro) which does
                     # not have the size key
                     pass
@@ -1137,7 +1137,7 @@ class RulesCheck(AppTool):
                     copper_list.append(elem_dict)
 
                 copper_name_2 = self.copper_b_object.currentText()
-                if copper_name_2 !='' and self.copper_b_cb.get_value():
+                if copper_name_2 != '' and self.copper_b_cb.get_value():
                     elem_dict = {}
                     elem_dict['name'] = deepcopy(copper_name_2)
                     elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_2).apertures)
@@ -1363,12 +1363,12 @@ class RulesCheck(AppTool):
                     top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures)
 
                 silk_bottom = self.ss_b_object.currentText()
-                if silk_bottom !=  '' and self.ss_b_cb.get_value():
+                if silk_bottom != '' and self.ss_b_cb.get_value():
                     bottom_dict['name'] = deepcopy(silk_bottom)
                     bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures)
 
                 copper_outline = self.outline_object.currentText()
-                if copper_outline !=  '' and self.out_cb.get_value():
+                if copper_outline != '' and self.out_cb.get_value():
                     outline_dict['name'] = deepcopy(copper_outline)
                     outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures)
 
@@ -1421,7 +1421,7 @@ class RulesCheck(AppTool):
 
                 if self.sm_t_cb.get_value():
                     solder_obj = self.sm_t_object.currentText()
-                    if solder_obj !=  '':
+                    if solder_obj != '':
                         sm_dict['name'] = deepcopy(solder_obj)
                         sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
 
@@ -1431,7 +1431,7 @@ class RulesCheck(AppTool):
                                                                         _("TOP -> Minimum Solder Mask Sliver"))))
                 if self.sm_b_cb.get_value():
                     solder_obj = self.sm_b_object.currentText()
-                    if solder_obj !=  '':
+                    if solder_obj != '':
                         sm_dict['name'] = deepcopy(solder_obj)
                         sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
 
@@ -1625,7 +1625,7 @@ class RulesCheck(AppTool):
             new_obj.source_file = txt
             new_obj.read_only = True
 
-        self.app.app_obj.new_object('document', name='Rules Check results', initialize=init, plot=False)
+        self.app.app_obj.new_object('document', name='Rules_check_results', initialize=init, plot=False)
 
     def reset_fields(self):
         # self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 130 - 131
AppTools/ToolSub.py

@@ -32,6 +32,11 @@ class ToolSub(AppTool):
 
     job_finished = QtCore.pyqtSignal(bool)
 
+    # the string param is the outname and the list is a list of tuples each being formed from the new_aperture_geometry
+    # list and the second element is also a list with possible geometry that needs to be added to the '0' aperture
+    # meaning geometry that was deformed
+    aperture_processing_finished = QtCore.pyqtSignal(str, list)
+
     toolName = _("Subtract Tool")
 
     def __init__(self, app):
@@ -218,18 +223,14 @@ class ToolSub(AppTool):
 
         self.sub_union = []
 
-        try:
-            self.intersect_btn.clicked.disconnect(self.on_grb_intersection_click)
-        except (TypeError, AttributeError):
-            pass
-        self.intersect_btn.clicked.connect(self.on_grb_intersection_click)
+        # multiprocessing
+        self.pool = self.app.pool
+        self.results = []
 
-        try:
-            self.intersect_geo_btn.clicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
+        self.intersect_btn.clicked.connect(self.on_grb_intersection_click)
         self.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click)
         self.job_finished.connect(self.on_job_finished)
+        self.aperture_processing_finished.connect(self.new_gerber_object)
         self.reset_button.clicked.connect(self.set_tool_ui)
 
     def install(self, icon=None, separator=None, **kwargs):
@@ -310,138 +311,141 @@ class ToolSub(AppTool):
         # crate the new_apertures dict structure
         for apid in self.target_grb_obj.apertures:
             self.new_apertures[apid] = {}
-            self.new_apertures[apid]['type'] = 'C'
-            self.new_apertures[apid]['size'] = self.target_grb_obj.apertures[apid]['size']
-            self.new_apertures[apid]['geometry'] = []
-
-        geo_solid_union_list = []
-        geo_follow_union_list = []
-        geo_clear_union_list = []
-
-        for apid1 in self.sub_grb_obj.apertures:
-            if 'geometry' in self.sub_grb_obj.apertures[apid1]:
-                for elem in self.sub_grb_obj.apertures[apid1]['geometry']:
-                    if 'solid' in elem:
-                        geo_solid_union_list.append(elem['solid'])
-                    if 'follow' in elem:
-                        geo_follow_union_list.append(elem['follow'])
-                    if 'clear' in elem:
-                        geo_clear_union_list.append(elem['clear'])
+            for key in self.target_grb_obj.apertures[apid]:
+                if key == 'geometry':
+                    self.new_apertures[apid]['geometry'] = []
+                else:
+                    self.new_apertures[apid][key] = self.target_grb_obj.apertures[apid][key]
 
-        self.app.inform.emit('%s' % _("Processing geometry from Subtractor Gerber object."))
-        self.sub_solid_union = cascaded_union(geo_solid_union_list)
-        self.sub_follow_union = cascaded_union(geo_follow_union_list)
-        self.sub_clear_union = cascaded_union(geo_clear_union_list)
+        def worker_job(app_obj):
+            for apid in self.target_grb_obj.apertures:
+                target_geo = self.target_grb_obj.apertures[apid]['geometry']
 
-        # add the promises
-        for apid in self.target_grb_obj.apertures:
-            self.promises.append(apid)
+                sub_geometry = {}
+                sub_geometry['solid'] = []
+                sub_geometry['clear'] = []
+                for s_apid in self.sub_grb_obj.apertures:
+                    for s_el in self.sub_grb_obj.apertures[s_apid]['geometry']:
+                        if "solid" in s_el:
+                            sub_geometry['solid'].append(s_el["solid"])
+                        if "clear" in s_el:
+                            sub_geometry['clear'].append(s_el["clear"])
 
-        # start the QTimer to check for promises with 0.5 second period check
-        self.periodic_check(500, reset=True)
+                self.results.append(
+                    self.pool.apply_async(self.aperture_intersection, args=(apid, target_geo, sub_geometry))
+                )
 
-        for apid in self.target_grb_obj.apertures:
-            geo = self.target_grb_obj.apertures[apid]['geometry']
-            self.app.worker_task.emit({'fcn': self.aperture_intersection, 'params': [apid, geo]})
+            output = []
+            for p in self.results:
+                res = p.get()
+                output.append(res)
+                app_obj.inform.emit('%s: %s...' % (_("Finished parsing geometry for aperture"), str(res[0])))
 
-    def aperture_intersection(self, apid, geo):
-        new_geometry = []
+            app_obj.inform.emit("%s" % _("Subtraction aperture processing finished."))
 
-        log.debug("Working on promise: %s" % str(apid))
+            outname = self.target_gerber_combo.currentText() + '_sub'
+            self.aperture_processing_finished.emit(outname, output)
 
-        with self.app.proc_container.new('%s: %s...' % (_("Parsing geometry for aperture"), str(apid))):
+        self.app.worker_task.emit({'fcn': worker_job, 'params': [self.app]})
 
-            for geo_el in geo:
-                new_el = {}
+    @staticmethod
+    def aperture_intersection(apid, target_geo, sub_geometry):
+        """
 
-                if 'solid' in geo_el:
-                    work_geo = geo_el['solid']
-                    if self.sub_solid_union:
-                        if work_geo.intersects(self.sub_solid_union):
-                            new_geo = work_geo.difference(self.sub_solid_union)
-                            new_geo = new_geo.buffer(0)
-                            if new_geo:
-                                if not new_geo.is_empty:
-                                    new_el['solid'] = new_geo
-                                else:
-                                    new_el['solid'] = work_geo
-                            else:
-                                new_el['solid'] = work_geo
-                        else:
-                            new_el['solid'] = work_geo
-                    else:
-                        new_el['solid'] = work_geo
-
-                if 'follow' in geo_el:
-                    work_geo = geo_el['follow']
-                    if self.sub_follow_union:
-                        if work_geo.intersects(self.sub_follow_union):
-                            new_geo = work_geo.difference(self.sub_follow_union)
-                            new_geo = new_geo.buffer(0)
-                            if new_geo:
-                                if not new_geo.is_empty:
-                                    new_el['follow'] = new_geo
-                                else:
-                                    new_el['follow'] = work_geo
-                            else:
-                                new_el['follow'] = work_geo
-                        else:
-                            new_el['follow'] = work_geo
-                    else:
-                        new_el['follow'] = work_geo
-
-                if 'clear' in geo_el:
-                    work_geo = geo_el['clear']
-                    if self.sub_clear_union:
-                        if work_geo.intersects(self.sub_clear_union):
-                            new_geo = work_geo.difference(self.sub_clear_union)
-                            new_geo = new_geo.buffer(0)
-                            if new_geo:
-                                if not new_geo.is_empty:
-                                    new_el['clear'] = new_geo
-                                else:
-                                    new_el['clear'] = work_geo
-                            else:
-                                new_el['clear'] = work_geo
-                        else:
-                            new_el['clear'] = work_geo
-                    else:
-                        new_el['clear'] = work_geo
+        :param apid:            the aperture id for which we process geometry
+        :type apid:             str
+        :param target_geo:      the geometry list that holds the geometry from which we subtract
+        :type target_geo:       list
+        :param sub_geometry:    the apertures dict that holds all the geometry that is subtracted
+        :type sub_geometry:     dict
+        :return:                (apid, unaffected_geometry lsit, affected_geometry list)
+        :rtype:                 tuple
+        """
 
-                new_geometry.append(deepcopy(new_el))
+        unafected_geo = []
+        affected_geo = []
+
+        is_modified = False
+        for geo_el in target_geo:
+            new_geo_el = {}
+            if "solid" in geo_el:
+                for sub_solid_geo in sub_geometry["solid"]:
+                    if geo_el["solid"].intersects(sub_solid_geo):
+                        new_geo = geo_el["solid"].difference(sub_solid_geo)
+                        if not new_geo.is_empty:
+                            geo_el["solid"] = new_geo
+                            is_modified = True
+
+                new_geo_el["solid"] = deepcopy(geo_el["solid"])
+
+            if "clear" in geo_el:
+                for sub_solid_geo in sub_geometry["clear"]:
+                    if geo_el["clear"].intersects(sub_solid_geo):
+                        new_geo = geo_el["clear"].difference(sub_solid_geo)
+                        if not new_geo.is_empty:
+                            geo_el["clear"] = new_geo
+                            is_modified = True
+
+                new_geo_el["clear"] = deepcopy(geo_el["clear"])
+
+            if is_modified:
+                affected_geo.append(new_geo_el)
+            else:
+                unafected_geo.append(geo_el)
 
-        self.app.inform.emit('%s: %s...' % (_("Finished parsing geometry for aperture"), str(apid)))
+        return apid, unafected_geo, affected_geo
 
-        if new_geometry:
-            while not self.new_apertures[apid]['geometry']:
-                self.new_apertures[apid]['geometry'] = deepcopy(new_geometry)
-                time.sleep(0.5)
+    def new_gerber_object(self, outname, output):
+        """
 
-        while True:
-            # removal from list is done in a multithreaded way therefore not always the removal can be done
-            # so we keep trying until it's done
-            if apid not in self.promises:
-                break
+        :param outname:     name for the new Gerber object
+        :type outname:      str
+        :param output:      a list made of tuples in format:
+                            (aperture id in the target Gerber, unaffected_geometry list, affected_geometry list)
+        :type output:       list
+        :return:
+        :rtype:
+        """
 
-            self.promises.remove(apid)
-            time.sleep(0.5)
+        def obj_init(grb_obj, app_obj):
 
-        log.debug("Promise fulfilled: %s" % str(apid))
+            grb_obj.apertures = deepcopy(self.new_apertures)
 
-    def new_gerber_object(self, outname):
+            if '0' not in grb_obj.apertures:
+                grb_obj.apertures['0'] = {}
+                grb_obj.apertures['0']['type'] = 'REG'
+                grb_obj.apertures['0']['size'] = 0.0
+                grb_obj.apertures['0']['geometry'] = []
+
+            for apid, apid_val in list(grb_obj.apertures.items()):
+                for t in output:
+                    new_apid = t[0]
+                    if apid == new_apid:
+                        surving_geo = t[1]
+                        modified_geo = t[2]
+                        if surving_geo:
+                            apid_val['geometry'] = deepcopy(surving_geo)
+                        else:
+                            grb_obj.apertures.pop(apid, None)
 
-        def obj_init(grb_obj, app_obj):
+                        if modified_geo:
+                            grb_obj.apertures['0']['geometry'] += modified_geo
 
-            grb_obj.apertures = deepcopy(self.new_apertures)
+            # delete the '0' aperture if it has no geometry
+            if not grb_obj.apertures['0']['geometry']:
+                grb_obj.apertures.pop('0', None)
 
             poly_buff = []
             follow_buff = []
-            for ap in self.new_apertures:
-                for elem in self.new_apertures[ap]['geometry']:
-                    poly_buff.append(elem['solid'])
-                    follow_buff.append(elem['follow'])
+            for ap in grb_obj.apertures:
+                for elem in grb_obj.apertures[ap]['geometry']:
+                    if 'solid' in elem:
+                        solid_geo = elem['solid']
+                        poly_buff.append(solid_geo)
+                    if 'follow' in elem:
+                        follow_buff.append(elem['follow'])
 
-            work_poly_buff = cascaded_union(poly_buff)
+            work_poly_buff = MultiPolygon(poly_buff)
             try:
                 poly_buff = work_poly_buff.buffer(0.0000001)
             except ValueError:
@@ -454,25 +458,22 @@ class ToolSub(AppTool):
 
             grb_obj.solid_geometry = deepcopy(poly_buff)
             grb_obj.follow_geometry = deepcopy(follow_buff)
+            grb_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
+                                                         local_use=grb_obj, use_thread=False)
 
         with self.app.proc_container.new(_("Generating new object ...")):
             ret = self.app.app_obj.new_object('gerber', outname, obj_init, autoselected=False)
             if ret == 'fail':
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _('Generating new object failed.'))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _('Generating new object failed.'))
                 return
 
             # GUI feedback
-            self.app.inform.emit('[success] %s: %s' %
-                                 (_("Created"), outname))
+            self.app.inform.emit('[success] %s: %s' % (_("Created"), outname))
 
             # cleanup
             self.new_apertures.clear()
             self.new_solid_geometry[:] = []
-            try:
-                self.sub_union[:] = []
-            except TypeError:
-                self.sub_union = []
+            self.results = []
 
     def on_geo_intersection_click(self):
         # reset previous values
@@ -739,11 +740,9 @@ class ToolSub(AppTool):
                 outname = self.target_geo_combo.currentText() + '_sub'
 
                 # intersection jobs finished, start the creation of solid_geometry
-                self.app.worker_task.emit({'fcn': self.new_geo_object,
-                                           'params': [outname]})
+                self.app.worker_task.emit({'fcn': self.new_geo_object, 'params': [outname]})
         else:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _('Generating new object failed.'))
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _('Generating new object failed.'))
 
     def reset_fields(self):
         self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 3 - 0
CHANGELOG.md

@@ -13,6 +13,9 @@ CHANGELOG for FlatCAM beta
 - updated the Tool Database class to have the Isolation Tool data
 - Isolation Tool - made to work the adding of tools from database
 - fixed some issues related to using the new Numerical... GUI elements
+- fixed issues in the Tool Subtract
+- remade Tool Subtract to use multiprocessing when processing geometry
+- the resulting Gerber file from Tool Subtract has now the attribute source_file populated
 
 27.05.2020