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

This commit is contained in:
Marius Stanciu 2019-04-24 14:11:15 +03:00 committed by Marius
parent 79b402d198
commit e4faa27351
4 changed files with 105 additions and 22 deletions

View File

@ -94,8 +94,8 @@ class App(QtCore.QObject):
log.addHandler(handler) log.addHandler(handler)
# Version # Version
version = 8.914 version = 8.915
version_date = "2019/04/23" 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.

View File

@ -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)

View File

@ -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()

View File

@ -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.pdf_decompressed[short_name] = ''
self.parsed_obj_dict = {}
obj_type = 'gerber'
# 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" short_name = short_name + "_exc"
# store the points here until reconstitution: keys are diameters and values are list of (x,y) coords # 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