- 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.
- 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

View File

@ -2341,7 +2341,8 @@ class FCTransform(FCShapeTool):
class FlatCAMGrbEditor(QtCore.QObject):
draw_shape_idx = -1
plot_finished = QtCore.pyqtSignal()
# plot_finished = QtCore.pyqtSignal()
mp_finished = QtCore.pyqtSignal(list)
def __init__(self, app):
assert isinstance(app, FlatCAMApp.App), \
@ -2956,6 +2957,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.edited_obj_name = ""
self.tool_row = 0
# Multiprocessing pool
self.pool = self.app.pool
# Multiprocessing results
self.results = list()
# A QTimer
self.plot_thread = None
@ -3007,6 +3014,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.array_type_combo.currentIndexChanged.connect(self.on_array_type_combo)
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
self.editor_active = False
@ -3789,133 +3798,176 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.gerber_obj.apertures = conv_apertures
self.gerber_obj.units = app_units
# ############################################################# ##
# APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY
# ############################################################# ##
# # and then add it to the storage elements (each storage elements is a member of a list
# 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.")
# list of clear geos that are to be applied to the entire file
global_clear_geo = []
def worker_job(app_obj):
with app_obj.app.proc_container.new('%s ...' % _("Loading Gerber into Editor")):
# ############################################################# ##
# APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY
# ############################################################# ##
# create one big geometry made out of all 'negative' (clear) polygons
for apid in self.gerber_obj.apertures:
# 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))
# list of clear geos that are to be applied to the entire file
global_clear_geo = []
global_clear_geo = MultiPolygon(global_clear_geo)
if isinstance(global_clear_geo, Polygon):
global_clear_geo = list(global_clear_geo)
# create one big geometry made out of all 'negative' (clear) polygons
for apid in app_obj.gerber_obj.apertures:
# 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
# for geo in global_clear_geo:
# self.shapes.add(shape=geo, color='black', face_color='#000000'+'AF', layer=0, tolerance=self.tolerance)
# self.shapes.redraw()
global_clear_geo = MultiPolygon(global_clear_geo)
if isinstance(global_clear_geo, Polygon):
global_clear_geo = list(global_clear_geo)
# we subtract the big "negative" (clear) geometry from each solid polygon but only the part of clear geometry
# that fits inside the solid. otherwise we may loose the solid
for apid in self.gerber_obj.apertures:
temp_solid_geometry = []
if 'geometry' in self.gerber_obj.apertures[apid]:
# for elem in self.gerber_obj.apertures[apid]['geometry']:
# if 'solid' in elem:
# solid_geo = elem['solid']
# for clear_geo in global_clear_geo:
# # 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
# # delete it
# if clear_geo.within(solid_geo):
# solid_geo = solid_geo.difference(clear_geo)
# try:
# for poly in solid_geo:
# new_elem = dict()
#
# new_elem['solid'] = poly
# if 'clear' in elem:
# new_elem['clear'] = poly
# if 'follow' in elem:
# new_elem['follow'] = poly
# temp_elem.append(deepcopy(new_elem))
# except TypeError:
# new_elem = dict()
# new_elem['solid'] = solid_geo
# if 'clear' in elem:
# new_elem['clear'] = solid_geo
# if 'follow' in elem:
# new_elem['follow'] = solid_geo
# temp_elem.append(deepcopy(new_elem))
for elem in self.gerber_obj.apertures[apid]['geometry']:
new_elem = dict()
if 'solid' in elem:
solid_geo = elem['solid']
# we subtract the big "negative" (clear) geometry from each solid polygon but only the part of
# clear geometry that fits inside the solid. otherwise we may loose the solid
for apid in app_obj.gerber_obj.apertures:
temp_solid_geometry = []
if 'geometry' in app_obj.gerber_obj.apertures[apid]:
# for elem in self.gerber_obj.apertures[apid]['geometry']:
# if 'solid' in elem:
# solid_geo = elem['solid']
# for clear_geo in global_clear_geo:
# # 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
# # delete it
# if clear_geo.within(solid_geo):
# solid_geo = solid_geo.difference(clear_geo)
# try:
# for poly in solid_geo:
# new_elem = dict()
#
# new_elem['solid'] = poly
# if 'clear' in elem:
# new_elem['clear'] = poly
# if 'follow' in elem:
# new_elem['follow'] = poly
# temp_elem.append(deepcopy(new_elem))
# except TypeError:
# new_elem = dict()
# new_elem['solid'] = solid_geo
# if 'clear' in elem:
# new_elem['clear'] = solid_geo
# if 'follow' in elem:
# new_elem['follow'] = solid_geo
# temp_elem.append(deepcopy(new_elem))
for elem in app_obj.gerber_obj.apertures[apid]['geometry']:
new_elem = dict()
if 'solid' in elem:
solid_geo = elem['solid']
for clear_geo in global_clear_geo:
# 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
# delete it
if clear_geo.within(solid_geo):
solid_geo = solid_geo.difference(clear_geo)
for clear_geo in global_clear_geo:
# 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 delete it
if clear_geo.within(solid_geo):
solid_geo = solid_geo.difference(clear_geo)
new_elem['solid'] = solid_geo
if 'clear' in elem:
new_elem['clear'] = elem['clear']
if 'follow' in elem:
new_elem['follow'] = elem['follow']
temp_solid_geometry.append(deepcopy(new_elem))
new_elem['solid'] = solid_geo
if 'clear' in elem:
new_elem['clear'] = elem['clear']
if 'follow' in elem:
new_elem['follow'] = elem['follow']
temp_solid_geometry.append(deepcopy(new_elem))
self.gerber_obj.apertures[apid]['geometry'] = deepcopy(temp_solid_geometry)
log.warning("Polygon difference done for %d apertures." % len(self.gerber_obj.apertures))
app_obj.gerber_obj.apertures[apid]['geometry'] = deepcopy(temp_solid_geometry)
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
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] = {}
# Loading the Geometry into Editor Storage
for ap_id, ap_dict in app_obj.gerber_obj.apertures.items():
app_obj.results.append(app_obj.pool.apply_async(app_obj.add_apertures, args=(ap_id, ap_dict)))
# 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))
output = list()
for p in app_obj.results:
output.append(p.get())
# Check promises and clear if exists
while True:
try:
self.grb_plot_promises.remove(aperture_id)
time.sleep(0.5)
except ValueError:
break
for elem in output:
app_obj.storage_dict[elem[0]] = deepcopy(elem[1])
# 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]})
app_obj.mp_finished.emit(output)
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.build_ui(first_run=True)
self.plot_all()
# 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')
# HACK: enabling/disabling the cursor seams to somehow update the shapes making them more 'solid'
# - perhaps is a bug in VisPy implementation
self.app.app_cursor.enabled = False
self.app.app_cursor.enabled = True
self.app.inform.emit('[success] %s' % _("Finished loading the Gerber object into the editor."))
def update_fcgerber(self):
"""
@ -4070,11 +4122,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.new_object("gerber", outname, obj_init)
except Exception as 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
self.app.inform.emit('[success] %s' %
_("Done. Gerber editing finished."))
self.app.inform.emit('[success] %s' % _("Done. Gerber editing finished."))
# make sure to clean the previous results
self.results = list()
def on_tool_select(self, tool):
"""
@ -4585,49 +4639,49 @@ class FlatCAMGrbEditor(QtCore.QObject):
color = color[:7] + 'AF'
self.shapes.add(shape=geometry, color=color, face_color=color, layer=0, tolerance=self.tolerance)
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.")
self.plot_thread = QtCore.QTimer()
self.plot_thread.setInterval(check_period)
self.plot_finished.connect(self.setup_ui_after_delayed_plot)
self.plot_thread.timeout.connect(self.check_plot_finished)
self.plot_thread.start()
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
then the UI is rebuilt accordingly.
:return:
"""
try:
if not self.grb_plot_promises:
self.plot_thread.stop()
self.plot_finished.emit()
log.debug("FlatCAMGrbEditor --> delayed_plot finished")
except Exception as e:
traceback.print_exc()
def setup_ui_after_delayed_plot(self):
self.plot_finished.disconnect()
# now that we have data, create the GUI interface and add it to the Tool Tab
self.build_ui(first_run=True)
self.plot_all()
# HACK: enabling/disabling the cursor seams to somehow update the shapes making them more 'solid'
# - perhaps is a bug in VisPy implementation
self.app.app_cursor.enabled = False
self.app.app_cursor.enabled = True
# 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.")
# self.plot_thread = QtCore.QTimer()
# self.plot_thread.setInterval(check_period)
# self.plot_finished.connect(self.setup_ui_after_delayed_plot)
# self.plot_thread.timeout.connect(self.check_plot_finished)
# self.plot_thread.start()
#
# 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
# then the UI is rebuilt accordingly.
# :return:
# """
#
# try:
# if not self.grb_plot_promises:
# self.plot_thread.stop()
# self.plot_finished.emit()
# log.debug("FlatCAMGrbEditor --> delayed_plot finished")
# except Exception as e:
# traceback.print_exc()
#
# def setup_ui_after_delayed_plot(self):
# self.plot_finished.disconnect()
#
# # now that we have data, create the GUI interface and add it to the Tool Tab
# self.build_ui(first_run=True)
# self.plot_all()
#
# # HACK: enabling/disabling the cursor seams to somehow update the shapes making them more 'solid'
# # - perhaps is a bug in VisPy implementation
# self.app.app_cursor.enabled = False
# self.app.app_cursor.enabled = True
def get_selected(self):
"""