Просмотр исходного кода

- PDF import tool: working in making the PDF layer rendering multithreaded in itself (one layer rendered on each worker)

Marius Stanciu 6 лет назад
Родитель
Сommit
e4faa27351
4 измененных файлов с 105 добавлено и 22 удалено
  1. 3 3
      FlatCAMApp.py
  2. 4 0
      README.md
  3. 16 3
      flatcamEditors/FlatCAMGrbEditor.py
  4. 82 16
      flatcamTools/ToolPDF.py

+ 3 - 3
FlatCAMApp.py

@@ -94,8 +94,8 @@ class App(QtCore.QObject):
     log.addHandler(handler)
     log.addHandler(handler)
 
 
     # Version
     # Version
-    version = 8.914
-    version_date = "2019/04/23"
+    version = 8.915
+    version_date = "2019/05/11"
     beta = True
     beta = True
 
 
     # current date now
     # current date now
@@ -2773,7 +2773,7 @@ class App(QtCore.QObject):
 
 
     def new_object(self, kind, name, initialize, active=True, fit=True, plot=True, autoselected=True):
     def new_object(self, kind, name, initialize, active=True, fit=True, plot=True, autoselected=True):
         """
         """
-        Creates a new specalized FlatCAMObj and attaches it to the application,
+        Creates a new specialized FlatCAMObj and attaches it to the application,
         this is, updates the GUI accordingly, any other records and plots it.
         this is, updates the GUI accordingly, any other records and plots it.
         This method is thread-safe.
         This method is thread-safe.
 
 

+ 4 - 0
README.md

@@ -9,6 +9,10 @@ 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)
+
 23.04.2019
 23.04.2019
 
 
 - Gerber Editor: added two new tools: Add Disc and Add SemiDisc (porting of Circle and Arc from Geometry Editor)
 - Gerber Editor: added two new tools: Add Disc and Add SemiDisc (porting of Circle and Arc from Geometry Editor)

+ 16 - 3
flatcamEditors/FlatCAMGrbEditor.py

@@ -2174,11 +2174,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
 
         # flag to show if the object was modified
         # flag to show if the object was modified
         self.is_modified = False
         self.is_modified = False
-
         self.edited_obj_name = ""
         self.edited_obj_name = ""
-
         self.tool_row = 0
         self.tool_row = 0
 
 
+        # A QTimer
+        self.plot_thread = None
+
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
         self.editor_active = False
 
 
@@ -3498,6 +3499,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
             self.shapes.add(shape=geometry, color=color, face_color=color+'AF', layer=0)
             self.shapes.add(shape=geometry, color=color, face_color=color+'AF', layer=0)
 
 
     def start_delayed_plot(self, check_period):
     def start_delayed_plot(self, check_period):
+        """
+        This function starts an QTImer and it will periodically check if all the workers finish the plotting functions
+
+        :param check_period: time at which to check periodically if all plots finished to be plotted
+        :return:
+        """
+
         # self.plot_thread = threading.Thread(target=lambda: self.check_plot_finished(check_period))
         # self.plot_thread = threading.Thread(target=lambda: self.check_plot_finished(check_period))
         # self.plot_thread.start()
         # self.plot_thread.start()
         log.debug("FlatCAMGrbEditor --> Delayed Plot started.")
         log.debug("FlatCAMGrbEditor --> Delayed Plot started.")
@@ -3507,7 +3515,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.plot_thread.start()
         self.plot_thread.start()
 
 
     def check_plot_finished(self):
     def check_plot_finished(self):
-        # print(self.grb_plot_promises)
+        """
+        If all the promises made are finished then all the shapes are in shapes_storage and can be plotted safely and
+        then the UI is rebuilt accordingly.
+        :return:
+        """
+
         try:
         try:
             if not self.grb_plot_promises:
             if not self.grb_plot_promises:
                 self.plot_thread.stop()
                 self.plot_thread.stop()

+ 82 - 16
flatcamTools/ToolPDF.py

@@ -107,8 +107,18 @@ class ToolPDF(FlatCAMTool):
         self.gs['line_width'] = []   # each element is a float
         self.gs['line_width'] = []   # each element is a float
 
 
         self.obj_dict = dict()
         self.obj_dict = dict()
-        self.pdf_parsed = ''
-        self.parsed_obj_dict = dict()
+
+        self.pdf_decompressed = {}
+
+        # key = file name and extension
+        # value is a dict to store the parsed content of the PDF
+        self.pdf_parsed = {}
+
+        # QTimer for periodic check
+        self.check_thread = None
+        # 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 = []
 
 
         # conversion factor to INCH
         # conversion factor to INCH
         self.point_to_unit_factor = 0.01388888888
         self.point_to_unit_factor = 0.01388888888
@@ -148,16 +158,24 @@ class ToolPDF(FlatCAMTool):
         if len(filenames) == 0:
         if len(filenames) == 0:
             self.app.inform.emit(_("[WARNING_NOTCL] Open PDF cancelled."))
             self.app.inform.emit(_("[WARNING_NOTCL] Open PDF cancelled."))
         else:
         else:
+            # start the parsing timer with a period of 1 second
+            self.periodic_check(1000)
+
             for filename in filenames:
             for filename in filenames:
                 if filename != '':
                 if filename != '':
-                    self.app.worker_task.emit({'fcn': self.open_pdf, 'params': [filename]})
+                    self.app.worker_task.emit({'fcn': self.open_pdf,
+                                               'params': [filename]})
 
 
     def open_pdf(self, filename):
     def open_pdf(self, filename):
-        new_name = filename.split('/')[-1].split('\\')[-1]
+        short_name = filename.split('/')[-1].split('\\')[-1]
+        self.parsing_promises.append(short_name)
+
+        self.pdf_parsed[short_name] = {}
+        self.pdf_parsed[short_name]['pdf'] = {}
+        self.pdf_parsed[short_name]['filename'] = filename
+
         self.obj_dict.clear()
         self.obj_dict.clear()
-        self.pdf_parsed = ''
-        self.parsed_obj_dict = {}
-        obj_type = 'gerber'
+        self.pdf_decompressed[short_name] = ''
 
 
         # the UNITS in PDF files are points and here we set the factor to convert them to real units (either MM or INCH)
         # the UNITS in PDF files are points and here we set the factor to convert them to real units (either MM or INCH)
         if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
         if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
@@ -177,27 +195,40 @@ class ToolPDF(FlatCAMTool):
                 log.debug(" PDF STREAM: %d\n" % stream_nr)
                 log.debug(" PDF STREAM: %d\n" % stream_nr)
                 s = s.strip(b'\r\n')
                 s = s.strip(b'\r\n')
                 try:
                 try:
-                    self.pdf_parsed += (zlib.decompress(s).decode('UTF-8') + '\r\n')
+                    self.pdf_decompressed[short_name] += (zlib.decompress(s).decode('UTF-8') + '\r\n')
                 except Exception as e:
                 except Exception as e:
                     log.debug("ToolPDF.open_pdf().obj_init() --> %s" % str(e))
                     log.debug("ToolPDF.open_pdf().obj_init() --> %s" % str(e))
 
 
-            self.parsed_obj_dict = self.parse_pdf(pdf_content=self.pdf_parsed)
+            self.pdf_parsed[short_name]['pdf'] = self.parse_pdf(pdf_content=self.pdf_decompressed[short_name])
 
 
-        for k in self.parsed_obj_dict:
-            ap_dict = deepcopy(self.parsed_obj_dict[k])
+
+        # 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
+
+    def layer_rendering(self, **kwargs):
+        filename = kwargs['filename']
+        parsed_content_dict = kwargs['pdf']
+        short_name = filename.split('/')[-1].split('\\')[-1]
+
+        for k in parsed_content_dict:
+            ap_dict = parsed_content_dict[k]
             if ap_dict:
             if ap_dict:
                 if k == 0:
                 if k == 0:
                     # Excellon
                     # Excellon
                     obj_type = 'excellon'
                     obj_type = 'excellon'
 
 
-                    new_name = new_name + "_exc"
-                    # store the points here until reconstitution: keys are diameters and values are list of (x,y) coords
+                    short_name = short_name + "_exc"
+                    # store the points here until reconstitution:
+                    # keys are diameters and values are list of (x,y) coords
                     points = {}
                     points = {}
 
 
                     def obj_init(exc_obj, app_obj):
                     def obj_init(exc_obj, app_obj):
                         # print(self.parsed_obj_dict[0])
                         # print(self.parsed_obj_dict[0])
 
 
-                        for geo in self.parsed_obj_dict[0]['0']['solid_geometry']:
+                        for geo in parsed_content_dict[k]['0']['solid_geometry']:
                             xmin, ymin, xmax, ymax = geo.bounds
                             xmin, ymin, xmax, ymax = geo.bounds
                             center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
                             center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
 
 
@@ -235,7 +266,7 @@ class ToolPDF(FlatCAMTool):
                         for tool in exc_obj.tools:
                         for tool in exc_obj.tools:
                             if exc_obj.tools[tool]['solid_geometry']:
                             if exc_obj.tools[tool]['solid_geometry']:
                                 return
                                 return
-                        app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % new_name)
+                        app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % short_name)
                         return "fail"
                         return "fail"
                 else:
                 else:
                     # Gerber
                     # Gerber
@@ -265,7 +296,7 @@ class ToolPDF(FlatCAMTool):
 
 
                 with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % (int(k) - 2)):
                 with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % (int(k) - 2)):
 
 
-                    ret = self.app.new_object(obj_type, new_name, obj_init, autoselected=False)
+                    ret = self.app.new_object(obj_type, short_name, obj_init, autoselected=False)
                     if ret == 'fail':
                     if ret == 'fail':
                         self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
                         self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
                         return
                         return
@@ -274,6 +305,41 @@ class ToolPDF(FlatCAMTool):
                     # GUI feedback
                     # GUI feedback
                     self.app.inform.emit(_("[success] Opened: %s") % filename)
                     self.app.inform.emit(_("[success] Opened: %s") % filename)
 
 
+
+    def periodic_check(self, check_period):
+        """
+        This function starts an QTimer and it will periodically check if parsing was done
+
+        :param check_period: time at which to check periodically if all plots finished to be plotted
+        :return:
+        """
+
+        # 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()
+        self.check_thread.setInterval(check_period)
+        self.check_thread.timeout.connect(self.periodic_check_handler)
+        self.check_thread.start()
+
+    def periodic_check_handler(self):
+        """
+        If the parsing worker finished that start multithreaded rendering
+        :return:
+        """
+
+        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:
+                        self.layer_rendering(pdf_content_dict=self.pdf_parsed[short_name])
+
+                log.debug("ToolPDF --> Periodic check finished.")
+        except Exception:
+            traceback.print_exc()
+
     def parse_pdf(self, pdf_content):
     def parse_pdf(self, pdf_content):
         path = dict()
         path = dict()
         path['lines'] = []      # it's a list of lines subpaths
         path['lines'] = []      # it's a list of lines subpaths