Explorar o código

- 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

Marius Stanciu %!s(int64=6) %!d(string=hai) anos
pai
achega
b11c67d453
Modificáronse 4 ficheiros con 160 adicións e 126 borrados
  1. 4 2
      FlatCAMApp.py
  2. 1 1
      FlatCAMWorkerStack.py
  3. 2 0
      README.md
  4. 153 123
      flatcamTools/ToolPDF.py

+ 4 - 2
FlatCAMApp.py

@@ -189,6 +189,8 @@ class App(QtCore.QObject):
 
         App.log.info("FlatCAM Starting...")
 
+        self.main_thread = QtWidgets.QApplication.instance().thread()
+
         ###################
         ### OS-specific ###
         ###################
@@ -1772,7 +1774,7 @@ class App(QtCore.QObject):
             self.thr2 = QtCore.QThread()
             self.worker_task.emit({'fcn': self.version_check,
                                    'params': []})
-            self.thr2.start()
+            self.thr2.start(QtCore.QThread.LowPriority)
 
 
         ####################################
@@ -2883,7 +2885,7 @@ class App(QtCore.QObject):
         FlatCAMApp.App.log.debug("Moving new object back to main thread.")
 
         # Move the object to the main thread and let the app know that it is available.
-        obj.moveToThread(QtWidgets.QApplication.instance().thread())
+        obj.moveToThread(self.main_thread)
         self.object_created.emit(obj, obj_plot, obj_autoselected)
 
         return obj

+ 1 - 1
FlatCAMWorkerStack.py

@@ -25,7 +25,7 @@ class WorkerStack(QtCore.QObject):
             thread.started.connect(worker.run)
             worker.task_completed.connect(self.on_task_completed)
 
-            thread.start()
+            thread.start(QtCore.QThread.LowPriority)
 
             self.workers.append(worker)
             self.threads.append(thread)

+ 2 - 0
README.md

@@ -12,6 +12,8 @@ CAD program, and create G-Code for Isolation routing.
 24.04.2019
 
 - 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
 
 23.04.2019
 

+ 153 - 123
flatcamTools/ToolPDF.py

@@ -113,7 +113,8 @@ class ToolPDF(FlatCAMTool):
         self.pdf_parsed = {}
 
         # QTimer for periodic check
-        self.check_thread = None
+        self.check_thread = QtCore.QTimer()
+
         # Every time a parser is started we add a promise; every time a parser finished we remove a promise
         # when empty we start the layer rendering
         self.parsing_promises = []
@@ -196,106 +197,112 @@ class ToolPDF(FlatCAMTool):
                     log.debug("ToolPDF.open_pdf().obj_init() --> %s" % str(e))
 
             self.pdf_parsed[short_name]['pdf'] = self.parse_pdf(pdf_content=self.pdf_decompressed[short_name])
+            # we used it, now we delete it
+            self.pdf_decompressed[short_name] = ''
 
         # removal from list is done in a multithreaded way therefore not always the removal can be done
         try:
             self.parsing_promises.remove(short_name)
         except:
             pass
+        self.app.inform.emit(_("[success] Opened: %s") % filename)
 
-    def layer_rendering(self, filename, parsed_content_dict):
-        short_name = filename.split('/')[-1].split('\\')[-1]
+    def layer_rendering_as_excellon(self, filename, ap_dict, layer_nr):
+        outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
+
+        # store the points here until reconstitution:
+        # keys are diameters and values are list of (x,y) coords
+        points = {}
+
+        def obj_init(exc_obj, app_obj):
 
-        for k in parsed_content_dict:
-            ap_dict = parsed_content_dict[k]
-            if parsed_content_dict[k]:
-                if k == 0:
-                    # Excellon
-                    obj_type = 'excellon'
-
-                    short_name = short_name + "_exc"
-                    # store the points here until reconstitution:
-                    # keys are diameters and values are list of (x,y) coords
-                    points = {}
-
-                    def obj_init(exc_obj, app_obj):
-
-                        for geo in parsed_content_dict[k]['0']['solid_geometry']:
-                            xmin, ymin, xmax, ymax = geo.bounds
-                            center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
-
-                            # for drill bits, even in INCH, it's enough 3 decimals
-                            correction_factor = 0.974
-                            dia = (xmax - xmin) * correction_factor
-                            dia = round(dia, 3)
-                            if dia in points:
-                                points[dia].append(center)
-                            else:
-                                points[dia] = [center]
-
-                        sorted_dia = sorted(points.keys())
-
-                        name_tool = 0
-                        for dia in sorted_dia:
-                            name_tool += 1
-
-                            # create tools dictionary
-                            spec = {"C": dia}
-                            spec['solid_geometry'] = []
-                            exc_obj.tools[str(name_tool)] = spec
-
-                            # create drill list of dictionaries
-                            for dia_points in points:
-                                if dia == dia_points:
-                                    for pt in points[dia_points]:
-                                        exc_obj.drills.append({'point': Point(pt), 'tool': str(name_tool)})
-                                    break
-
-                        ret = exc_obj.create_geometry()
-                        if ret == 'fail':
-                            log.debug("Could not create geometry for Excellon object.")
-                            return "fail"
-                        for tool in exc_obj.tools:
-                            if exc_obj.tools[tool]['solid_geometry']:
-                                return
-                        app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % short_name)
-                        return "fail"
+            for geo in ap_dict['0']['solid_geometry']:
+                xmin, ymin, xmax, ymax = geo.bounds
+                center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
+
+                # for drill bits, even in INCH, it's enough 3 decimals
+                correction_factor = 0.974
+                dia = (xmax - xmin) * correction_factor
+                dia = round(dia, 3)
+                if dia in points:
+                    points[dia].append(center)
                 else:
-                    # Gerber
-                    obj_type = 'gerber'
-
-                    def obj_init(grb_obj, app_obj):
-
-                        grb_obj.apertures = ap_dict
-
-                        poly_buff = []
-                        for ap in grb_obj.apertures:
-                            for k in grb_obj.apertures[ap]:
-                                if k == 'solid_geometry':
-                                    poly_buff += ap_dict[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)
-
-                with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % (int(k) - 2)):
-
-                    ret = self.app.new_object(obj_type, short_name, obj_init, autoselected=False)
-                    if ret == 'fail':
-                        self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
-                        return
-                    # Register recent file
-                    self.app.file_opened.emit(obj_type, filename)
-                    # GUI feedback
-                    self.app.inform.emit(_("[success] Opened: %s") % filename)
+                    points[dia] = [center]
+
+            sorted_dia = sorted(points.keys())
+
+            name_tool = 0
+            for dia in sorted_dia:
+                name_tool += 1
+
+                # create tools dictionary
+                spec = {"C": dia}
+                spec['solid_geometry'] = []
+                exc_obj.tools[str(name_tool)] = spec
+
+                # create drill list of dictionaries
+                for dia_points in points:
+                    if dia == dia_points:
+                        for pt in points[dia_points]:
+                            exc_obj.drills.append({'point': Point(pt), 'tool': str(name_tool)})
+                        break
+
+            ret = exc_obj.create_geometry()
+            if ret == 'fail':
+                log.debug("Could not create geometry for Excellon object.")
+                return "fail"
+            for tool in exc_obj.tools:
+                if exc_obj.tools[tool]['solid_geometry']:
+                    return
+            app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % outname)
+            return "fail"
+
+        with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
+
+            ret = self.app.new_object("excellon", outname, obj_init, autoselected=False)
+            if ret == 'fail':
+                self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
+                return
+            # Register recent file
+            self.app.file_opened.emit("excellon", filename)
+            # GUI feedback
+            self.app.inform.emit(_("[success] Rendered: %s") % outname)
+
+    def layer_rendering_as_gerber(self, filename, ap_dict, layer_nr):
+        outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
+
+        def obj_init(grb_obj, app_obj):
+
+            grb_obj.apertures = ap_dict
+
+            poly_buff = []
+            for ap in grb_obj.apertures:
+                for k in grb_obj.apertures[ap]:
+                    if k == 'solid_geometry':
+                        poly_buff += ap_dict[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)
+
+        with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
+
+            ret = self.app.new_object('gerber', outname, obj_init, autoselected=False)
+            if ret == 'fail':
+                self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
+                return
+            # Register recent file
+            self.app.file_opened.emit('gerber', filename)
+            # GUI feedback
+            self.app.inform.emit(_("[success] Rendered: %s") % outname)
 
     def periodic_check(self, check_period):
         """
@@ -308,28 +315,53 @@ class ToolPDF(FlatCAMTool):
         # self.plot_thread = threading.Thread(target=lambda: self.check_plot_finished(check_period))
         # self.plot_thread.start()
         log.debug("ToolPDF --> Periodic Check started.")
-        self.check_thread = QtCore.QTimer()
+
+        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()
+        self.check_thread.start(QtCore.QThread.HighPriority)
 
     def periodic_check_handler(self):
         """
-        If the parsing worker finished that start multithreaded rendering
+        If the parsing worker finished then start multithreaded rendering
         :return:
         """
-        log.debug("checking parsing --> %s" % str(self.parsing_promises))
+        # log.debug("checking parsing --> %s" % str(self.parsing_promises))
+
         try:
             if not self.parsing_promises:
                 self.check_thread.stop()
                 # parsing finished start the layer rendering
                 if self.pdf_parsed:
-
-                    for short_name in self.pdf_parsed:
-                        filename = self.pdf_parsed[short_name]['filename']
-                        pdf = self.pdf_parsed[short_name]['pdf']
-                        self.app.worker_task.emit({'fcn': self.layer_rendering,
-                                                   'params': [filename, pdf]})
+                    obj_to_delete = []
+                    for object_name in self.pdf_parsed:
+                        filename = deepcopy(self.pdf_parsed[object_name]['filename'])
+                        pdf_content = deepcopy(self.pdf_parsed[object_name]['pdf'])
+                        obj_to_delete.append(object_name)
+                        for k in pdf_content:
+                            ap_dict = pdf_content[k]
+                            if ap_dict:
+                                layer_nr = k
+                                if k == 0:
+                                    self.app.worker_task.emit({'fcn': self.layer_rendering_as_excellon,
+                                                               'params': [filename, ap_dict, layer_nr]})
+                                else:
+                                    self.app.worker_task.emit({'fcn': self.layer_rendering_as_gerber,
+                                                               'params': [filename, ap_dict, layer_nr]})
+                    # delete the object already processed so it will not be processed again for other objects
+                    # that were opened at the same time; like in drag & drop on GUI
+                    for obj_name in obj_to_delete:
+                        if obj_name in self.pdf_parsed:
+                            self.pdf_parsed.pop(obj_name)
 
                 log.debug("ToolPDF --> Periodic check finished.")
         except Exception:
@@ -362,9 +394,10 @@ class ToolPDF(FlatCAMTool):
 
         # store the objects to be transformed into Gerbers
         object_dict = {}
-
         # will serve as key in the object_dict
         layer_nr = 1
+        # create first object
+        object_dict[layer_nr] = {}
 
         # store the apertures here
         apertures_dict = {}
@@ -381,10 +414,6 @@ class ToolPDF(FlatCAMTool):
         clear_apertures_dict['0']['type'] = 'C'
         clear_apertures_dict['0']['solid_geometry'] = []
 
-        # create first object
-        object_dict[layer_nr] = {}
-        layer_nr += 1
-
         # on stroke color change we create a new apertures dictionary and store the old one in a storage from where
         # it will be transformed into Gerber object
         old_color = [None, None ,None]
@@ -411,11 +440,12 @@ class ToolPDF(FlatCAMTool):
                     # same color, do nothing
                     continue
                 else:
-                    object_dict[layer_nr] = deepcopy(apertures_dict)
-                    apertures_dict.clear()
-                    layer_nr += 1
+                    if apertures_dict:
+                        object_dict[layer_nr] = deepcopy(apertures_dict)
+                        apertures_dict.clear()
+                        layer_nr += 1
 
-                    object_dict[layer_nr] = dict()
+                        object_dict[layer_nr] = dict()
                 old_color = copy(color)
                 # we make sure that the following geometry is added to the right storage
                 flag_clear_geo = False
@@ -807,18 +837,18 @@ class ToolPDF(FlatCAMTool):
                     if path['rectangle']:
                         for subp in path['rectangle']:
                             geo = copy(subp)
-                            # close the subpath if it was not closed already
-                            if close_subpath is False and start_point is not None:
-                                geo.append(start_point)
+                            # # close the subpath if it was not closed already
+                            # if close_subpath is False and start_point is not None:
+                            #     geo.append(start_point)
                             geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                             path_geo.append(geo_el)
                         # the path was painted therefore initialize it
                         path['rectangle'] = []
                     else:
                         geo = copy(subpath['rectangle'])
-                        # close the subpath if it was not closed already
-                        if close_subpath is False and start_point is not None:
-                            geo.append(start_point)
+                        # # close the subpath if it was not closed already
+                        # if close_subpath is False and start_point is not None:
+                        #     geo.append(start_point)
                         geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                         path_geo.append(geo_el)
                         subpath['rectangle'] = []
@@ -929,9 +959,9 @@ class ToolPDF(FlatCAMTool):
                         # fill
                         for subp in path['rectangle']:
                             geo = copy(subp)
-                            # close the subpath if it was not closed already
-                            if close_subpath is False:
-                                geo.append(geo[0])
+                            # # close the subpath if it was not closed already
+                            # if close_subpath is False:
+                            #     geo.append(geo[0])
                             geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                             fill_geo.append(geo_el)
                         # stroke
@@ -944,9 +974,9 @@ class ToolPDF(FlatCAMTool):
                     else:
                         # fill
                         geo = copy(subpath['rectangle'])
-                        # close the subpath if it was not closed already
-                        if close_subpath is False:
-                            geo.append(start_point)
+                        # # close the subpath if it was not closed already
+                        # if close_subpath is False:
+                        #     geo.append(start_point)
                         geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                         fill_geo.append(geo_el)
                         # stroke