- 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)
# Version
version = 8.914
version_date = "2019/04/23"
version = 8.915
version_date = "2019/05/11"
beta = True
# 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):
"""
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 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
- 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
self.is_modified = False
self.edited_obj_name = ""
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
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)
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.start()
log.debug("FlatCAMGrbEditor --> Delayed Plot started.")
@ -3507,7 +3515,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.plot_thread.start()
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:
if not self.grb_plot_promises:
self.plot_thread.stop()

View File

@ -107,8 +107,18 @@ class ToolPDF(FlatCAMTool):
self.gs['line_width'] = [] # each element is a float
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
self.point_to_unit_factor = 0.01388888888
@ -148,16 +158,24 @@ class ToolPDF(FlatCAMTool):
if len(filenames) == 0:
self.app.inform.emit(_("[WARNING_NOTCL] Open PDF cancelled."))
else:
# start the parsing timer with a period of 1 second
self.periodic_check(1000)
for filename in filenames:
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):
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.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)
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)
s = s.strip(b'\r\n')
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:
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 k == 0:
# 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 = {}
def obj_init(exc_obj, app_obj):
# 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
center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
@ -235,7 +266,7 @@ class ToolPDF(FlatCAMTool):
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") % new_name)
app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % short_name)
return "fail"
else:
# Gerber
@ -265,7 +296,7 @@ class ToolPDF(FlatCAMTool):
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':
self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
return
@ -274,6 +305,41 @@ class ToolPDF(FlatCAMTool):
# GUI feedback
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):
path = dict()
path['lines'] = [] # it's a list of lines subpaths