- fixed issues in the Tool Subtract

- remade Tool Subtract to use multiprocessing when processing geometry
- the resulting Gerber file from Tool Subtract has now the attribute source_file populated
This commit is contained in:
Marius Stanciu 2020-05-29 00:50:19 +03:00 committed by Marius
parent 8e687c5054
commit 8d16bebf44
3 changed files with 136 additions and 134 deletions

View File

@ -951,7 +951,7 @@ class RulesCheck(AppTool):
geo = geo_el['solid'] geo = geo_el['solid']
pt = geo.representative_point() pt = geo.representative_point()
points_list.append((pt.x, pt.y)) points_list.append((pt.x, pt.y))
except Exception as e: except Exception:
# An exception will be raised for the 'size' key in case of apertures of type AM (macro) which does # An exception will be raised for the 'size' key in case of apertures of type AM (macro) which does
# not have the size key # not have the size key
pass pass
@ -1137,7 +1137,7 @@ class RulesCheck(AppTool):
copper_list.append(elem_dict) copper_list.append(elem_dict)
copper_name_2 = self.copper_b_object.currentText() copper_name_2 = self.copper_b_object.currentText()
if copper_name_2 !='' and self.copper_b_cb.get_value(): if copper_name_2 != '' and self.copper_b_cb.get_value():
elem_dict = {} elem_dict = {}
elem_dict['name'] = deepcopy(copper_name_2) elem_dict['name'] = deepcopy(copper_name_2)
elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_2).apertures) elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_2).apertures)
@ -1363,12 +1363,12 @@ class RulesCheck(AppTool):
top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures) top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures)
silk_bottom = self.ss_b_object.currentText() silk_bottom = self.ss_b_object.currentText()
if silk_bottom != '' and self.ss_b_cb.get_value(): if silk_bottom != '' and self.ss_b_cb.get_value():
bottom_dict['name'] = deepcopy(silk_bottom) bottom_dict['name'] = deepcopy(silk_bottom)
bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures) bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures)
copper_outline = self.outline_object.currentText() copper_outline = self.outline_object.currentText()
if copper_outline != '' and self.out_cb.get_value(): if copper_outline != '' and self.out_cb.get_value():
outline_dict['name'] = deepcopy(copper_outline) outline_dict['name'] = deepcopy(copper_outline)
outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures) outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures)
@ -1421,7 +1421,7 @@ class RulesCheck(AppTool):
if self.sm_t_cb.get_value(): if self.sm_t_cb.get_value():
solder_obj = self.sm_t_object.currentText() solder_obj = self.sm_t_object.currentText()
if solder_obj != '': if solder_obj != '':
sm_dict['name'] = deepcopy(solder_obj) sm_dict['name'] = deepcopy(solder_obj)
sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures) sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
@ -1431,7 +1431,7 @@ class RulesCheck(AppTool):
_("TOP -> Minimum Solder Mask Sliver")))) _("TOP -> Minimum Solder Mask Sliver"))))
if self.sm_b_cb.get_value(): if self.sm_b_cb.get_value():
solder_obj = self.sm_b_object.currentText() solder_obj = self.sm_b_object.currentText()
if solder_obj != '': if solder_obj != '':
sm_dict['name'] = deepcopy(solder_obj) sm_dict['name'] = deepcopy(solder_obj)
sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures) sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
@ -1625,7 +1625,7 @@ class RulesCheck(AppTool):
new_obj.source_file = txt new_obj.source_file = txt
new_obj.read_only = True new_obj.read_only = True
self.app.app_obj.new_object('document', name='Rules Check results', initialize=init, plot=False) self.app.app_obj.new_object('document', name='Rules_check_results', initialize=init, plot=False)
def reset_fields(self): def reset_fields(self):
# self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) # self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

View File

@ -32,6 +32,11 @@ class ToolSub(AppTool):
job_finished = QtCore.pyqtSignal(bool) job_finished = QtCore.pyqtSignal(bool)
# the string param is the outname and the list is a list of tuples each being formed from the new_aperture_geometry
# list and the second element is also a list with possible geometry that needs to be added to the '0' aperture
# meaning geometry that was deformed
aperture_processing_finished = QtCore.pyqtSignal(str, list)
toolName = _("Subtract Tool") toolName = _("Subtract Tool")
def __init__(self, app): def __init__(self, app):
@ -218,18 +223,14 @@ class ToolSub(AppTool):
self.sub_union = [] self.sub_union = []
try: # multiprocessing
self.intersect_btn.clicked.disconnect(self.on_grb_intersection_click) self.pool = self.app.pool
except (TypeError, AttributeError): self.results = []
pass
self.intersect_btn.clicked.connect(self.on_grb_intersection_click)
try: self.intersect_btn.clicked.connect(self.on_grb_intersection_click)
self.intersect_geo_btn.clicked.disconnect()
except (TypeError, AttributeError):
pass
self.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click) self.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click)
self.job_finished.connect(self.on_job_finished) self.job_finished.connect(self.on_job_finished)
self.aperture_processing_finished.connect(self.new_gerber_object)
self.reset_button.clicked.connect(self.set_tool_ui) self.reset_button.clicked.connect(self.set_tool_ui)
def install(self, icon=None, separator=None, **kwargs): def install(self, icon=None, separator=None, **kwargs):
@ -310,138 +311,141 @@ class ToolSub(AppTool):
# crate the new_apertures dict structure # crate the new_apertures dict structure
for apid in self.target_grb_obj.apertures: for apid in self.target_grb_obj.apertures:
self.new_apertures[apid] = {} self.new_apertures[apid] = {}
self.new_apertures[apid]['type'] = 'C' for key in self.target_grb_obj.apertures[apid]:
self.new_apertures[apid]['size'] = self.target_grb_obj.apertures[apid]['size'] if key == 'geometry':
self.new_apertures[apid]['geometry'] = [] self.new_apertures[apid]['geometry'] = []
else:
self.new_apertures[apid][key] = self.target_grb_obj.apertures[apid][key]
geo_solid_union_list = [] def worker_job(app_obj):
geo_follow_union_list = [] for apid in self.target_grb_obj.apertures:
geo_clear_union_list = [] target_geo = self.target_grb_obj.apertures[apid]['geometry']
for apid1 in self.sub_grb_obj.apertures: sub_geometry = {}
if 'geometry' in self.sub_grb_obj.apertures[apid1]: sub_geometry['solid'] = []
for elem in self.sub_grb_obj.apertures[apid1]['geometry']: sub_geometry['clear'] = []
if 'solid' in elem: for s_apid in self.sub_grb_obj.apertures:
geo_solid_union_list.append(elem['solid']) for s_el in self.sub_grb_obj.apertures[s_apid]['geometry']:
if 'follow' in elem: if "solid" in s_el:
geo_follow_union_list.append(elem['follow']) sub_geometry['solid'].append(s_el["solid"])
if 'clear' in elem: if "clear" in s_el:
geo_clear_union_list.append(elem['clear']) sub_geometry['clear'].append(s_el["clear"])
self.app.inform.emit('%s' % _("Processing geometry from Subtractor Gerber object.")) self.results.append(
self.sub_solid_union = cascaded_union(geo_solid_union_list) self.pool.apply_async(self.aperture_intersection, args=(apid, target_geo, sub_geometry))
self.sub_follow_union = cascaded_union(geo_follow_union_list) )
self.sub_clear_union = cascaded_union(geo_clear_union_list)
# add the promises output = []
for apid in self.target_grb_obj.apertures: for p in self.results:
self.promises.append(apid) res = p.get()
output.append(res)
app_obj.inform.emit('%s: %s...' % (_("Finished parsing geometry for aperture"), str(res[0])))
# start the QTimer to check for promises with 0.5 second period check app_obj.inform.emit("%s" % _("Subtraction aperture processing finished."))
self.periodic_check(500, reset=True)
for apid in self.target_grb_obj.apertures: outname = self.target_gerber_combo.currentText() + '_sub'
geo = self.target_grb_obj.apertures[apid]['geometry'] self.aperture_processing_finished.emit(outname, output)
self.app.worker_task.emit({'fcn': self.aperture_intersection, 'params': [apid, geo]})
def aperture_intersection(self, apid, geo): self.app.worker_task.emit({'fcn': worker_job, 'params': [self.app]})
new_geometry = []
log.debug("Working on promise: %s" % str(apid)) @staticmethod
def aperture_intersection(apid, target_geo, sub_geometry):
"""
with self.app.proc_container.new('%s: %s...' % (_("Parsing geometry for aperture"), str(apid))): :param apid: the aperture id for which we process geometry
:type apid: str
:param target_geo: the geometry list that holds the geometry from which we subtract
:type target_geo: list
:param sub_geometry: the apertures dict that holds all the geometry that is subtracted
:type sub_geometry: dict
:return: (apid, unaffected_geometry lsit, affected_geometry list)
:rtype: tuple
"""
for geo_el in geo: unafected_geo = []
new_el = {} affected_geo = []
if 'solid' in geo_el: is_modified = False
work_geo = geo_el['solid'] for geo_el in target_geo:
if self.sub_solid_union: new_geo_el = {}
if work_geo.intersects(self.sub_solid_union): if "solid" in geo_el:
new_geo = work_geo.difference(self.sub_solid_union) for sub_solid_geo in sub_geometry["solid"]:
new_geo = new_geo.buffer(0) if geo_el["solid"].intersects(sub_solid_geo):
if new_geo: new_geo = geo_el["solid"].difference(sub_solid_geo)
if not new_geo.is_empty: if not new_geo.is_empty:
new_el['solid'] = new_geo geo_el["solid"] = new_geo
else: is_modified = True
new_el['solid'] = work_geo
else:
new_el['solid'] = work_geo
else:
new_el['solid'] = work_geo
else:
new_el['solid'] = work_geo
if 'follow' in geo_el: new_geo_el["solid"] = deepcopy(geo_el["solid"])
work_geo = geo_el['follow']
if self.sub_follow_union:
if work_geo.intersects(self.sub_follow_union):
new_geo = work_geo.difference(self.sub_follow_union)
new_geo = new_geo.buffer(0)
if new_geo:
if not new_geo.is_empty:
new_el['follow'] = new_geo
else:
new_el['follow'] = work_geo
else:
new_el['follow'] = work_geo
else:
new_el['follow'] = work_geo
else:
new_el['follow'] = work_geo
if 'clear' in geo_el: if "clear" in geo_el:
work_geo = geo_el['clear'] for sub_solid_geo in sub_geometry["clear"]:
if self.sub_clear_union: if geo_el["clear"].intersects(sub_solid_geo):
if work_geo.intersects(self.sub_clear_union): new_geo = geo_el["clear"].difference(sub_solid_geo)
new_geo = work_geo.difference(self.sub_clear_union) if not new_geo.is_empty:
new_geo = new_geo.buffer(0) geo_el["clear"] = new_geo
if new_geo: is_modified = True
if not new_geo.is_empty:
new_el['clear'] = new_geo
else:
new_el['clear'] = work_geo
else:
new_el['clear'] = work_geo
else:
new_el['clear'] = work_geo
else:
new_el['clear'] = work_geo
new_geometry.append(deepcopy(new_el)) new_geo_el["clear"] = deepcopy(geo_el["clear"])
self.app.inform.emit('%s: %s...' % (_("Finished parsing geometry for aperture"), str(apid))) if is_modified:
affected_geo.append(new_geo_el)
else:
unafected_geo.append(geo_el)
if new_geometry: return apid, unafected_geo, affected_geo
while not self.new_apertures[apid]['geometry']:
self.new_apertures[apid]['geometry'] = deepcopy(new_geometry)
time.sleep(0.5)
while True: def new_gerber_object(self, outname, output):
# removal from list is done in a multithreaded way therefore not always the removal can be done """
# so we keep trying until it's done
if apid not in self.promises:
break
self.promises.remove(apid) :param outname: name for the new Gerber object
time.sleep(0.5) :type outname: str
:param output: a list made of tuples in format:
log.debug("Promise fulfilled: %s" % str(apid)) (aperture id in the target Gerber, unaffected_geometry list, affected_geometry list)
:type output: list
def new_gerber_object(self, outname): :return:
:rtype:
"""
def obj_init(grb_obj, app_obj): def obj_init(grb_obj, app_obj):
grb_obj.apertures = deepcopy(self.new_apertures) grb_obj.apertures = deepcopy(self.new_apertures)
if '0' not in grb_obj.apertures:
grb_obj.apertures['0'] = {}
grb_obj.apertures['0']['type'] = 'REG'
grb_obj.apertures['0']['size'] = 0.0
grb_obj.apertures['0']['geometry'] = []
for apid, apid_val in list(grb_obj.apertures.items()):
for t in output:
new_apid = t[0]
if apid == new_apid:
surving_geo = t[1]
modified_geo = t[2]
if surving_geo:
apid_val['geometry'] = deepcopy(surving_geo)
else:
grb_obj.apertures.pop(apid, None)
if modified_geo:
grb_obj.apertures['0']['geometry'] += modified_geo
# delete the '0' aperture if it has no geometry
if not grb_obj.apertures['0']['geometry']:
grb_obj.apertures.pop('0', None)
poly_buff = [] poly_buff = []
follow_buff = [] follow_buff = []
for ap in self.new_apertures: for ap in grb_obj.apertures:
for elem in self.new_apertures[ap]['geometry']: for elem in grb_obj.apertures[ap]['geometry']:
poly_buff.append(elem['solid']) if 'solid' in elem:
follow_buff.append(elem['follow']) solid_geo = elem['solid']
poly_buff.append(solid_geo)
if 'follow' in elem:
follow_buff.append(elem['follow'])
work_poly_buff = cascaded_union(poly_buff) work_poly_buff = MultiPolygon(poly_buff)
try: try:
poly_buff = work_poly_buff.buffer(0.0000001) poly_buff = work_poly_buff.buffer(0.0000001)
except ValueError: except ValueError:
@ -454,25 +458,22 @@ class ToolSub(AppTool):
grb_obj.solid_geometry = deepcopy(poly_buff) grb_obj.solid_geometry = deepcopy(poly_buff)
grb_obj.follow_geometry = deepcopy(follow_buff) grb_obj.follow_geometry = deepcopy(follow_buff)
grb_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
local_use=grb_obj, use_thread=False)
with self.app.proc_container.new(_("Generating new object ...")): with self.app.proc_container.new(_("Generating new object ...")):
ret = self.app.app_obj.new_object('gerber', outname, obj_init, autoselected=False) ret = self.app.app_obj.new_object('gerber', outname, obj_init, autoselected=False)
if ret == 'fail': if ret == 'fail':
self.app.inform.emit('[ERROR_NOTCL] %s' % self.app.inform.emit('[ERROR_NOTCL] %s' % _('Generating new object failed.'))
_('Generating new object failed.'))
return return
# GUI feedback # GUI feedback
self.app.inform.emit('[success] %s: %s' % self.app.inform.emit('[success] %s: %s' % (_("Created"), outname))
(_("Created"), outname))
# cleanup # cleanup
self.new_apertures.clear() self.new_apertures.clear()
self.new_solid_geometry[:] = [] self.new_solid_geometry[:] = []
try: self.results = []
self.sub_union[:] = []
except TypeError:
self.sub_union = []
def on_geo_intersection_click(self): def on_geo_intersection_click(self):
# reset previous values # reset previous values
@ -739,11 +740,9 @@ class ToolSub(AppTool):
outname = self.target_geo_combo.currentText() + '_sub' outname = self.target_geo_combo.currentText() + '_sub'
# intersection jobs finished, start the creation of solid_geometry # intersection jobs finished, start the creation of solid_geometry
self.app.worker_task.emit({'fcn': self.new_geo_object, self.app.worker_task.emit({'fcn': self.new_geo_object, 'params': [outname]})
'params': [outname]})
else: else:
self.app.inform.emit('[ERROR_NOTCL] %s' % self.app.inform.emit('[ERROR_NOTCL] %s' % _('Generating new object failed.'))
_('Generating new object failed.'))
def reset_fields(self): def reset_fields(self):
self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

View File

@ -13,6 +13,9 @@ CHANGELOG for FlatCAM beta
- updated the Tool Database class to have the Isolation Tool data - updated the Tool Database class to have the Isolation Tool data
- Isolation Tool - made to work the adding of tools from database - Isolation Tool - made to work the adding of tools from database
- fixed some issues related to using the new Numerical... GUI elements - fixed some issues related to using the new Numerical... GUI elements
- fixed issues in the Tool Subtract
- remade Tool Subtract to use multiprocessing when processing geometry
- the resulting Gerber file from Tool Subtract has now the attribute source_file populated
27.05.2020 27.05.2020