- remade the Gerber Editor way to import an Gerber object into the editor in such a way to use the multiprocessing

This commit is contained in:
Marius
2019-12-03 15:36:10 +02:00
committed by Marius Stanciu
parent 4a2f06ae3e
commit 685413209f
2 changed files with 214 additions and 159 deletions

View File

@@ -13,6 +13,7 @@ CAD program, and create G-Code for Isolation routing.
- in Preferences added an Apply button which apply the modified preferences but does not save to a file, minimizing the file IO operations; CTRL+S key combo does the Apply now. - in Preferences added an Apply button which apply the modified preferences but does not save to a file, minimizing the file IO operations; CTRL+S key combo does the Apply now.
- updated some of the default values to metric, values that were missed previously - updated some of the default values to metric, values that were missed previously
- remade the Gerber Editor way to import an Gerber object into the editor in such a way to use the multiprocessing
2.12.2019 2.12.2019

View File

@@ -2341,7 +2341,8 @@ class FCTransform(FCShapeTool):
class FlatCAMGrbEditor(QtCore.QObject): class FlatCAMGrbEditor(QtCore.QObject):
draw_shape_idx = -1 draw_shape_idx = -1
plot_finished = QtCore.pyqtSignal() # plot_finished = QtCore.pyqtSignal()
mp_finished = QtCore.pyqtSignal(list)
def __init__(self, app): def __init__(self, app):
assert isinstance(app, FlatCAMApp.App), \ assert isinstance(app, FlatCAMApp.App), \
@@ -2956,6 +2957,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.edited_obj_name = "" self.edited_obj_name = ""
self.tool_row = 0 self.tool_row = 0
# Multiprocessing pool
self.pool = self.app.pool
# Multiprocessing results
self.results = list()
# A QTimer # A QTimer
self.plot_thread = None self.plot_thread = None
@@ -3007,6 +3014,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.array_type_combo.currentIndexChanged.connect(self.on_array_type_combo) self.array_type_combo.currentIndexChanged.connect(self.on_array_type_combo)
self.pad_axis_radio.activated_custom.connect(self.on_linear_angle_radio) self.pad_axis_radio.activated_custom.connect(self.on_linear_angle_radio)
self.mp_finished.connect(self.on_multiprocessing_finished)
# 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
@@ -3789,133 +3798,176 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.gerber_obj.apertures = conv_apertures self.gerber_obj.apertures = conv_apertures
self.gerber_obj.units = app_units self.gerber_obj.units = app_units
# ############################################################# ## # # and then add it to the storage elements (each storage elements is a member of a list
# APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY # def job_thread(aperture_id):
# ############################################################# ## # with self.app.proc_container.new('%s: %s ...' %
# (_("Adding geometry for aperture"), str(aperture_id))):
# storage_elem = []
# self.storage_dict[aperture_id] = {}
#
# # add the Gerber geometry to editor storage
# for k, v in self.gerber_obj.apertures[aperture_id].items():
# try:
# if k == 'geometry':
# for geo_el in v:
# if geo_el:
# self.add_gerber_shape(DrawToolShape(geo_el), storage_elem)
# self.storage_dict[aperture_id][k] = storage_elem
# else:
# self.storage_dict[aperture_id][k] = self.gerber_obj.apertures[aperture_id][k]
# except Exception as e:
# log.debug("FlatCAMGrbEditor.edit_fcgerber().job_thread() --> %s" % str(e))
#
# # Check promises and clear if exists
# while True:
# try:
# self.grb_plot_promises.remove(aperture_id)
# time.sleep(0.5)
# except ValueError:
# break
#
# # we create a job work each aperture, job that work in a threaded way to store the geometry in local storage
# # as DrawToolShapes
# for ap_id in self.gerber_obj.apertures:
# self.grb_plot_promises.append(ap_id)
# self.app.worker_task.emit({'fcn': job_thread, 'params': [ap_id]})
#
# self.set_ui()
#
# # do the delayed plot only if there is something to plot (the gerber is not empty)
# try:
# if bool(self.gerber_obj.apertures):
# self.start_delayed_plot(check_period=1000)
# else:
# raise AttributeError
# except AttributeError:
# # now that we have data (empty data actually), create the GUI interface and add it to the Tool Tab
# self.build_ui(first_run=True)
# # and add the first aperture to have something to play with
# self.on_aperture_add('10')
# log.warning("Applying clear geometry in the apertures dict.") def worker_job(app_obj):
# list of clear geos that are to be applied to the entire file with app_obj.app.proc_container.new('%s ...' % _("Loading Gerber into Editor")):
global_clear_geo = [] # ############################################################# ##
# APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY
# ############################################################# ##
# create one big geometry made out of all 'negative' (clear) polygons # list of clear geos that are to be applied to the entire file
for apid in self.gerber_obj.apertures: global_clear_geo = []
# first check if we have any clear_geometry (LPC) and if yes added it to the global_clear_geo
if 'geometry' in self.gerber_obj.apertures[apid]:
for elem in self.gerber_obj.apertures[apid]['geometry']:
if 'clear' in elem:
global_clear_geo.append(elem['clear'])
log.warning("Found %d clear polygons." % len(global_clear_geo))
global_clear_geo = MultiPolygon(global_clear_geo) # create one big geometry made out of all 'negative' (clear) polygons
if isinstance(global_clear_geo, Polygon): for apid in app_obj.gerber_obj.apertures:
global_clear_geo = list(global_clear_geo) # first check if we have any clear_geometry (LPC) and if yes added it to the global_clear_geo
if 'geometry' in app_obj.gerber_obj.apertures[apid]:
for elem in app_obj.gerber_obj.apertures[apid]['geometry']:
if 'clear' in elem:
global_clear_geo.append(elem['clear'])
log.warning("Found %d clear polygons." % len(global_clear_geo))
# for debugging global_clear_geo = MultiPolygon(global_clear_geo)
# for geo in global_clear_geo: if isinstance(global_clear_geo, Polygon):
# self.shapes.add(shape=geo, color='black', face_color='#000000'+'AF', layer=0, tolerance=self.tolerance) global_clear_geo = list(global_clear_geo)
# self.shapes.redraw()
# we subtract the big "negative" (clear) geometry from each solid polygon but only the part of clear geometry # we subtract the big "negative" (clear) geometry from each solid polygon but only the part of
# that fits inside the solid. otherwise we may loose the solid # clear geometry that fits inside the solid. otherwise we may loose the solid
for apid in self.gerber_obj.apertures: for apid in app_obj.gerber_obj.apertures:
temp_solid_geometry = [] temp_solid_geometry = []
if 'geometry' in self.gerber_obj.apertures[apid]: if 'geometry' in app_obj.gerber_obj.apertures[apid]:
# for elem in self.gerber_obj.apertures[apid]['geometry']: # for elem in self.gerber_obj.apertures[apid]['geometry']:
# if 'solid' in elem: # if 'solid' in elem:
# solid_geo = elem['solid'] # solid_geo = elem['solid']
# for clear_geo in global_clear_geo: # for clear_geo in global_clear_geo:
# # Make sure that the clear_geo is within the solid_geo otherwise we loose # # Make sure that the clear_geo is within the solid_geo otherwise we loose
# # the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to # # the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to
# # delete it # # delete it
# if clear_geo.within(solid_geo): # if clear_geo.within(solid_geo):
# solid_geo = solid_geo.difference(clear_geo) # solid_geo = solid_geo.difference(clear_geo)
# try: # try:
# for poly in solid_geo: # for poly in solid_geo:
# new_elem = dict() # new_elem = dict()
# #
# new_elem['solid'] = poly # new_elem['solid'] = poly
# if 'clear' in elem: # if 'clear' in elem:
# new_elem['clear'] = poly # new_elem['clear'] = poly
# if 'follow' in elem: # if 'follow' in elem:
# new_elem['follow'] = poly # new_elem['follow'] = poly
# temp_elem.append(deepcopy(new_elem)) # temp_elem.append(deepcopy(new_elem))
# except TypeError: # except TypeError:
# new_elem = dict() # new_elem = dict()
# new_elem['solid'] = solid_geo # new_elem['solid'] = solid_geo
# if 'clear' in elem: # if 'clear' in elem:
# new_elem['clear'] = solid_geo # new_elem['clear'] = solid_geo
# if 'follow' in elem: # if 'follow' in elem:
# new_elem['follow'] = solid_geo # new_elem['follow'] = solid_geo
# temp_elem.append(deepcopy(new_elem)) # temp_elem.append(deepcopy(new_elem))
for elem in self.gerber_obj.apertures[apid]['geometry']: for elem in app_obj.gerber_obj.apertures[apid]['geometry']:
new_elem = dict() new_elem = dict()
if 'solid' in elem: if 'solid' in elem:
solid_geo = elem['solid'] solid_geo = elem['solid']
for clear_geo in global_clear_geo: for clear_geo in global_clear_geo:
# Make sure that the clear_geo is within the solid_geo otherwise we loose # Make sure that the clear_geo is within the solid_geo otherwise we loose
# the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to # the solid_geometry. We want for clear_geometry just to cut into solid_geometry
# delete it # not to delete it
if clear_geo.within(solid_geo): if clear_geo.within(solid_geo):
solid_geo = solid_geo.difference(clear_geo) solid_geo = solid_geo.difference(clear_geo)
new_elem['solid'] = solid_geo new_elem['solid'] = solid_geo
if 'clear' in elem: if 'clear' in elem:
new_elem['clear'] = elem['clear'] new_elem['clear'] = elem['clear']
if 'follow' in elem: if 'follow' in elem:
new_elem['follow'] = elem['follow'] new_elem['follow'] = elem['follow']
temp_solid_geometry.append(deepcopy(new_elem)) temp_solid_geometry.append(deepcopy(new_elem))
self.gerber_obj.apertures[apid]['geometry'] = deepcopy(temp_solid_geometry) app_obj.gerber_obj.apertures[apid]['geometry'] = deepcopy(temp_solid_geometry)
log.warning("Polygon difference done for %d apertures." % len(self.gerber_obj.apertures)) log.warning("Polygon difference done for %d apertures." % len(app_obj.gerber_obj.apertures))
# and then add it to the storage elements (each storage elements is a member of a list # Loading the Geometry into Editor Storage
def job_thread(aperture_id): for ap_id, ap_dict in app_obj.gerber_obj.apertures.items():
with self.app.proc_container.new('%s: %s ...' % app_obj.results.append(app_obj.pool.apply_async(app_obj.add_apertures, args=(ap_id, ap_dict)))
(_("Adding geometry for aperture"), str(aperture_id))):
storage_elem = []
self.storage_dict[aperture_id] = {}
# add the Gerber geometry to editor storage output = list()
for k, v in self.gerber_obj.apertures[aperture_id].items(): for p in app_obj.results:
try: output.append(p.get())
if k == 'geometry':
for geo_el in v:
if geo_el:
self.add_gerber_shape(DrawToolShape(geo_el), storage_elem)
self.storage_dict[aperture_id][k] = storage_elem
else:
self.storage_dict[aperture_id][k] = self.gerber_obj.apertures[aperture_id][k]
except Exception as e:
log.debug("FlatCAMGrbEditor.edit_fcgerber().job_thread() --> %s" % str(e))
# Check promises and clear if exists for elem in output:
while True: app_obj.storage_dict[elem[0]] = deepcopy(elem[1])
try:
self.grb_plot_promises.remove(aperture_id)
time.sleep(0.5)
except ValueError:
break
# we create a job work each aperture, job that work in a threaded way to store the geometry in local storage app_obj.mp_finished.emit(output)
# as DrawToolShapes
for ap_id in self.gerber_obj.apertures:
self.grb_plot_promises.append(ap_id)
self.app.worker_task.emit({'fcn': job_thread, 'params': [ap_id]})
self.app.worker_task.emit({'fcn': worker_job, 'params': [self]})
@staticmethod
def add_apertures(aperture_id, aperture_dict):
storage_elem = list()
storage_dict = dict()
for k, v in list(aperture_dict.items()):
try:
if k == 'geometry':
for geo_el in v:
if geo_el:
storage_elem.append(DrawToolShape(geo_el))
storage_dict[k] = storage_elem
else:
storage_dict[k] = aperture_dict[k]
except Exception as e:
log.debug("FlatCAMGrbEditor.edit_fcgerber().job_thread() --> %s" % str(e))
return [aperture_id, storage_dict]
def on_multiprocessing_finished(self):
self.app.proc_container.update_view_text(' %s' % _("Setting up the UI"))
self.app.inform.emit('[success] %s.' % _("Adding geometry finished. Preparing the GUI"))
self.set_ui() self.set_ui()
self.build_ui(first_run=True)
self.plot_all()
# do the delayed plot only if there is something to plot (the gerber is not empty) # HACK: enabling/disabling the cursor seams to somehow update the shapes making them more 'solid'
try: # - perhaps is a bug in VisPy implementation
if bool(self.gerber_obj.apertures): self.app.app_cursor.enabled = False
self.start_delayed_plot(check_period=1000) self.app.app_cursor.enabled = True
else: self.app.inform.emit('[success] %s' % _("Finished loading the Gerber object into the editor."))
raise AttributeError
except AttributeError:
# now that we have data (empty data actually), create the GUI interface and add it to the Tool Tab
self.build_ui(first_run=True)
# and add the first aperture to have something to play with
self.on_aperture_add('10')
def update_fcgerber(self): def update_fcgerber(self):
""" """
@@ -4070,11 +4122,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.new_object("gerber", outname, obj_init) self.app.new_object("gerber", outname, obj_init)
except Exception as e: except Exception as e:
log.error("Error on Edited object creation: %s" % str(e)) log.error("Error on Edited object creation: %s" % str(e))
self.app.progress.emit(100) # make sure to clean the previous results
self.results = list()
return return
self.app.inform.emit('[success] %s' % self.app.inform.emit('[success] %s' % _("Done. Gerber editing finished."))
_("Done. Gerber editing finished.")) # make sure to clean the previous results
self.results = list()
def on_tool_select(self, tool): def on_tool_select(self, tool):
""" """
@@ -4585,49 +4639,49 @@ class FlatCAMGrbEditor(QtCore.QObject):
color = color[:7] + 'AF' color = color[:7] + 'AF'
self.shapes.add(shape=geometry, color=color, face_color=color, layer=0, tolerance=self.tolerance) self.shapes.add(shape=geometry, color=color, face_color=color, layer=0, tolerance=self.tolerance)
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 # 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 # :param check_period: time at which to check periodically if all plots finished to be plotted
:return: # :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.")
self.plot_thread = QtCore.QTimer() # self.plot_thread = QtCore.QTimer()
self.plot_thread.setInterval(check_period) # self.plot_thread.setInterval(check_period)
self.plot_finished.connect(self.setup_ui_after_delayed_plot) # self.plot_finished.connect(self.setup_ui_after_delayed_plot)
self.plot_thread.timeout.connect(self.check_plot_finished) # self.plot_thread.timeout.connect(self.check_plot_finished)
self.plot_thread.start() # self.plot_thread.start()
#
def check_plot_finished(self): # def check_plot_finished(self):
""" # """
If all the promises made are finished then all the shapes are in shapes_storage and can be plotted safely and # 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. # then the UI is rebuilt accordingly.
:return: # :return:
""" # """
#
try: # try:
if not self.grb_plot_promises: # if not self.grb_plot_promises:
self.plot_thread.stop() # self.plot_thread.stop()
self.plot_finished.emit() # self.plot_finished.emit()
log.debug("FlatCAMGrbEditor --> delayed_plot finished") # log.debug("FlatCAMGrbEditor --> delayed_plot finished")
except Exception as e: # except Exception as e:
traceback.print_exc() # traceback.print_exc()
#
def setup_ui_after_delayed_plot(self): # def setup_ui_after_delayed_plot(self):
self.plot_finished.disconnect() # self.plot_finished.disconnect()
#
# now that we have data, create the GUI interface and add it to the Tool Tab # # now that we have data, create the GUI interface and add it to the Tool Tab
self.build_ui(first_run=True) # self.build_ui(first_run=True)
self.plot_all() # self.plot_all()
#
# HACK: enabling/disabling the cursor seams to somehow update the shapes making them more 'solid' # # HACK: enabling/disabling the cursor seams to somehow update the shapes making them more 'solid'
# - perhaps is a bug in VisPy implementation # # - perhaps is a bug in VisPy implementation
self.app.app_cursor.enabled = False # self.app.app_cursor.enabled = False
self.app.app_cursor.enabled = True # self.app.app_cursor.enabled = True
def get_selected(self): def get_selected(self):
""" """