- 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
This commit is contained in:
Marius Stanciu
2019-04-24 22:26:13 +03:00
parent c0716f22e5
commit b11c67d453
4 changed files with 150 additions and 116 deletions

View File

@@ -189,6 +189,8 @@ class App(QtCore.QObject):
App.log.info("FlatCAM Starting...") App.log.info("FlatCAM Starting...")
self.main_thread = QtWidgets.QApplication.instance().thread()
################### ###################
### OS-specific ### ### OS-specific ###
################### ###################
@@ -1772,7 +1774,7 @@ class App(QtCore.QObject):
self.thr2 = QtCore.QThread() self.thr2 = QtCore.QThread()
self.worker_task.emit({'fcn': self.version_check, self.worker_task.emit({'fcn': self.version_check,
'params': []}) '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.") 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. # 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) self.object_created.emit(obj, obj_plot, obj_autoselected)
return obj return obj

View File

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

View File

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

View File

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