diff --git a/README.md b/README.md index caffba76..d6f45125 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ CAD program, and create G-Code for Isolation routing. - added support to use Multi Processing (multi core usage, not simple threading) in Rules Check Tool - in Rules Check Tool added the functionality for the following rules: Hole Size, Trace Size, Hole to Hole Clearance - in Rules Check Tool added the functionality for Copper to Copper Clearance +- in Rules Check Tool added the functionality for Copper to Outline Clearance, Silk to Silk Clearance, Silk to Solder Mask Clearance, Silk to Outline Clearance, Minimum Solder Mask Sliver, Minimum Annular Ring 29.09.2019 diff --git a/flatcamTools/ToolRulesCheck.py b/flatcamTools/ToolRulesCheck.py index a1f76bbe..5c051f0b 100644 --- a/flatcamTools/ToolRulesCheck.py +++ b/flatcamTools/ToolRulesCheck.py @@ -263,7 +263,7 @@ class RulesCheck(FlatCAMTool): self.ts = OptionalInputSection(self.trace_size_cb, [self.trace_size_lbl, self.trace_size_entry]) # Copper2copper clearance - self.clearance_copper2copper_cb = FCCheckBox('%s:' % _("Copper to copper clearance")) + self.clearance_copper2copper_cb = FCCheckBox('%s:' % _("Copper to Copper clearance")) self.clearance_copper2copper_cb.setToolTip( _("This checks if the minimum clearance between copper\n" "features is met.") @@ -281,46 +281,8 @@ class RulesCheck(FlatCAMTool): self.c2c = OptionalInputSection( self.clearance_copper2copper_cb, [self.clearance_copper2copper_lbl, self.clearance_copper2copper_entry]) - # Copper2soldermask clearance - self.clearance_copper2sm_cb = FCCheckBox('%s:' % _("Copper to soldermask clearance")) - self.clearance_copper2sm_cb.setToolTip( - _("This checks if the minimum clearance between copper\n" - "features and soldermask features is met.") - ) - self.form_layout_1.addRow(self.clearance_copper2sm_cb) - - # Copper2soldermask clearance value - self.clearance_copper2sm_entry = FCEntry() - self.clearance_copper2sm_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.clearance_copper2sm_lbl.setToolTip( - _("Minimum acceptable clearance value.") - ) - self.form_layout_1.addRow(self.clearance_copper2sm_lbl, self.clearance_copper2sm_entry) - - self.c2sm = OptionalInputSection( - self.clearance_copper2sm_cb, [self.clearance_copper2sm_lbl, self.clearance_copper2sm_entry]) - - # Copper2silkscreen clearance - self.clearance_copper2sk_cb = FCCheckBox('%s:' % _("Copper to silkscreen clearance")) - self.clearance_copper2sk_cb.setToolTip( - _("This checks if the minimum clearance between copper\n" - "features and silkscreen features is met.") - ) - self.form_layout_1.addRow(self.clearance_copper2sk_cb) - - # Copper2silkscreen clearance value - self.clearance_copper2sk_entry = FCEntry() - self.clearance_copper2sk_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.clearance_copper2sk_lbl.setToolTip( - _("Minimum acceptable clearance value.") - ) - self.form_layout_1.addRow(self.clearance_copper2sk_lbl, self.clearance_copper2sk_entry) - - self.c2sk = OptionalInputSection( - self.clearance_copper2sk_cb, [self.clearance_copper2sk_lbl, self.clearance_copper2sk_entry]) - # Copper2outline clearance - self.clearance_copper2ol_cb = FCCheckBox('%s:' % _("Copper to outline clearance")) + self.clearance_copper2ol_cb = FCCheckBox('%s:' % _("Copper to Outline clearance")) self.clearance_copper2ol_cb.setToolTip( _("This checks if the minimum clearance between copper\n" "features and the outline is met.") @@ -595,7 +557,7 @@ class RulesCheck(FlatCAMTool): self.reset_fields() @staticmethod - def check_gerber_clearance(gerber_1, gerber_2, size, rule): + def check_inside_gerber_clearance(gerber_obj, size, rule): rule_title = rule violations = list() @@ -605,6 +567,82 @@ class RulesCheck(FlatCAMTool): 'points': list() }) + # added it so I won't have errors of using before declaring + gerber_2 = dict() + + if not gerber_obj: + return 'Fail. Not enough Gerber objects to check Gerber 2 Gerber clearance' + + total_geo = list() + for apid in gerber_obj['apertures']: + if 'geometry' in gerber_obj['apertures'][apid]: + geometry = gerber_obj['apertures'][apid]['geometry'] + for geo_el in geometry: + if 'solid' in geo_el and geo_el['solid'] is not None: + total_geo.append(geo_el['solid']) + + total_geo = MultiPolygon(total_geo) + total_geo = total_geo.buffer(0) + + iterations = len(total_geo) + iterations = (iterations * (iterations - 1)) / 2 + + log.debug("RulesCheck.check_gerber_clearance(). Iterations: %s" % str(iterations)) + + min_dict = dict() + idx = 1 + for geo in total_geo: + for s_geo in total_geo[idx:]: + # minimize the number of distances by not taking into considerations those that are too small + dist = geo.distance(s_geo) + if float(dist) < float(size): + loc_1, loc_2 = nearest_points(geo, s_geo) + + dx = loc_1.x - loc_2.x + dy = loc_1.y - loc_2.y + loc = min(loc_1.x, loc_2.x) + (abs(dx) / 2), min(loc_1.y, loc_2.y) + (abs(dy) / 2) + + if dist in min_dict: + min_dict[dist].append(loc) + else: + min_dict[dist] = [loc] + idx += 1 + + points_list = list() + for dist in min_dict.keys(): + for location in min_dict[dist]: + points_list.append(location) + + obj_violations['name'] = gerber_obj['name'] + obj_violations['points'] = points_list + violations.append(deepcopy(obj_violations)) + + return rule_title, violations + + @staticmethod + def check_gerber_clearance(gerber_list, size, rule): + rule_title = rule + + violations = list() + obj_violations = dict() + obj_violations.update({ + 'name': '', + 'points': list() + }) + + # added it so I won't have errors of using before declaring + gerber_2 = dict() + + if len(gerber_list) == 2: + gerber_1 = gerber_list[0] + gerber_3 = gerber_list[1] + elif len(gerber_list) == 3: + gerber_1 = gerber_list[0] + gerber_2 = gerber_list[1] + gerber_3 = gerber_list[2] + else: + return 'Fail. Not enough Gerber objects to check Gerber 2 Gerber clearance' + total_geo_grb_1 = list() for apid in gerber_1['apertures']: if 'geometry' in gerber_1['apertures'][apid]: @@ -613,20 +651,29 @@ class RulesCheck(FlatCAMTool): if 'solid' in geo_el and geo_el['solid'] is not None: total_geo_grb_1.append(geo_el['solid']) - total_geo_grb_2= list() - for apid in gerber_2['apertures']: - if 'geometry' in gerber_2['apertures'][apid]: - geometry = gerber_2['apertures'][apid]['geometry'] + if len(gerber_list) == 3: + # add the second Gerber geometry to the first one if it exists + for apid in gerber_2['apertures']: + if 'geometry' in gerber_2['apertures'][apid]: + geometry = gerber_2['apertures'][apid]['geometry'] + for geo_el in geometry: + if 'solid' in geo_el and geo_el['solid'] is not None: + total_geo_grb_1.append(geo_el['solid']) + + total_geo_grb_3= list() + for apid in gerber_3['apertures']: + if 'geometry' in gerber_3['apertures'][apid]: + geometry = gerber_3['apertures'][apid]['geometry'] for geo_el in geometry: if 'solid' in geo_el and geo_el['solid'] is not None: - total_geo_grb_2.append(geo_el['solid']) + total_geo_grb_3.append(geo_el['solid']) - iterations = len(total_geo_grb_1) * len(total_geo_grb_2) + iterations = len(total_geo_grb_1) * len(total_geo_grb_3) log.debug("RulesCheck.check_gerber_clearance(). Iterations: %s" % str(iterations)) min_dict = dict() for geo in total_geo_grb_1: - for s_geo in total_geo_grb_2: + for s_geo in total_geo_grb_3: # minimize the number of distances by not taking into considerations those that are too small dist = geo.distance(s_geo) if float(dist) < float(size): @@ -766,6 +813,87 @@ class RulesCheck(FlatCAMTool): return rule, violations + @staticmethod + def check_gerber_annular_ring(obj_list, size, rule): + rule_title = rule + + violations = list() + obj_violations = dict() + obj_violations.update({ + 'name': '', + 'points': list() + }) + + # added it so I won't have errors of using before declaring + gerber_extra_obj = dict() + + if len(obj_list) == 2: + gerber_obj = obj_list[0] + exc_obj = obj_list[1] + elif len(obj_list) == 3: + gerber_obj = obj_list[0] + gerber_extra_obj = obj_list[1] + exc_obj = obj_list[2] + else: + return 'Fail. Not enough objects to check Minimum Annular Ring' + + total_geo_grb = list() + for apid in gerber_obj['apertures']: + if 'geometry' in gerber_obj['apertures'][apid]: + geometry = gerber_obj['apertures'][apid]['geometry'] + for geo_el in geometry: + if 'solid' in geo_el and geo_el['solid'] is not None: + total_geo_grb.append(geo_el['solid']) + + if len(obj_list) == 3: + # add the second Gerber geometry to the first one if it exists + for apid in gerber_extra_obj['apertures']: + if 'geometry' in gerber_extra_obj['apertures'][apid]: + geometry = gerber_extra_obj['apertures'][apid]['geometry'] + for geo_el in geometry: + if 'solid' in geo_el and geo_el['solid'] is not None: + total_geo_grb.append(geo_el['solid']) + + total_geo_exc = list() + for tool in exc_obj['tools']: + if 'solid_geometry' in exc_obj['tools'][tool]: + geometry = exc_obj['tools'][tool]['solid_geometry'] + for geo in geometry: + total_geo_exc.append(geo) + + iterations = len(total_geo_grb) * len(total_geo_exc) + log.debug("RulesCheck.check_gerber_annular_ring(). Iterations: %s" % str(iterations)) + + min_dict = dict() + for geo in total_geo_grb: + for s_geo in total_geo_exc: + # minimize the number of distances by not taking into considerations those that are too small + dist = geo.distance(s_geo) + if float(dist) < float(size): + loc_1, loc_2 = nearest_points(geo, s_geo) + + dx = loc_1.x - loc_2.x + dy = loc_1.y - loc_2.y + loc = min(loc_1.x, loc_2.x) + (abs(dx) / 2), min(loc_1.y, loc_2.y) + (abs(dy) / 2) + + if dist in min_dict: + min_dict[dist].append(loc) + else: + min_dict[dist] = [loc] + + points_list = list() + for dist in min_dict.keys(): + for location in min_dict[dist]: + points_list.append(location) + + name_list = [gerber_obj['name'], exc_obj['name']] + + obj_violations['name'] = name_list + obj_violations['points'] = points_list + violations.append(deepcopy(obj_violations)) + + return rule_title, violations + def execute(self): self.results = list() @@ -796,8 +924,49 @@ class RulesCheck(FlatCAMTool): # RULE: Check Copper to Copper Clearance if self.clearance_copper2copper_cb.get_value(): + copper_dict = dict() + + try: + copper_copper_clearance = float(self.clearance_copper2copper_entry.get_value()) + except Exception as e: + log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Copper to Copper clearance"), + _("Value is not valid."))) + return + + if self.copper_t_cb.get_value(): + copper_obj = self.copper_t_object.currentText() + if copper_obj is not '': + copper_dict['name'] = deepcopy(copper_obj) + copper_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_obj).apertures) + + self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, + args=(copper_dict, + copper_copper_clearance, + _("TOP: Copper to Copper clearance")))) + if self.copper_b_cb.get_value(): + copper_obj = self.copper_b_object.currentText() + if copper_obj is not '': + copper_dict['name'] = deepcopy(copper_obj) + copper_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_obj).apertures) + + self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, + args=(copper_dict, + copper_copper_clearance, + _("BOTTOM: Copper to Copper clearance")))) + + if self.copper_t_cb.get_value() is False and self.copper_b_cb.get_value() is False: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Copper to Copper clearance"), + _("At least one Gerber object has to be selected for this rule but none is selected."))) + return + + # RULE: Check Copper to Outline Clearance + if self.clearance_copper2ol_cb.get_value(): top_dict = dict() bottom_dict = dict() + outline_dict = dict() copper_top = self.copper_t_object.currentText() if copper_top is not '' and self.copper_t_cb.get_value(): @@ -809,22 +978,303 @@ class RulesCheck(FlatCAMTool): bottom_dict['name'] = deepcopy(copper_bottom) bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_bottom).apertures) + copper_outline = self.outline_object.currentText() + if copper_outline is not '' and self.out_cb.get_value(): + outline_dict['name'] = deepcopy(copper_outline) + outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures) + try: - copper_clearance = float(self.clearance_copper2copper_entry.get_value()) + copper_outline_clearance = float(self.clearance_copper2ol_entry.get_value()) except Exception as e: log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) - self.app.inform.emit('%s. %s' % (_("Copper to Copper clearance"), _("Value is not valid."))) + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Copper to Outline clearance"), + _("Value is not valid."))) return - if not top_dict or not bottom_dict: - self.app.inform.emit('%s. %s' % (_("Copper to Copper clearance"), - _("One or both copper Gerber objects is not valid."))) + if not top_dict and not bottom_dict or not outline_dict: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Copper to Outline clearance"), + _("One of the copper Gerber objects or the Outline Gerber object is not valid."))) return + objs = [] + if top_dict: + objs.append(top_dict) + if bottom_dict: + objs.append(bottom_dict) + + if outline_dict: + objs.append(outline_dict) + else: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Copper to Outline clearance"), + _("Outline Gerber object presence is mandatory for this rule but it is not selected."))) + return + self.results.append(self.pool.apply_async(self.check_gerber_clearance, - args=(top_dict, - bottom_dict, - copper_clearance, - _("Copper to copper clearance")))) + args=(objs, + copper_outline_clearance, + _("Copper to Outline clearance")))) + + # RULE: Check Silk to Silk Clearance + if self.clearance_silk2silk_cb.get_value(): + silk_dict = dict() + + try: + silk_silk_clearance = float(self.clearance_silk2silk_entry.get_value()) + except Exception as e: + log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Silk to Silk clearance"), + _("Value is not valid."))) + return + + if self.ss_t_cb.get_value(): + silk_obj = self.ss_t_object.currentText() + if silk_obj is not '': + silk_dict['name'] = deepcopy(silk_obj) + silk_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_obj).apertures) + + self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, + args=(silk_dict, + silk_silk_clearance, + _("TOP: Silk to Silk clearance")))) + if self.ss_b_cb.get_value(): + silk_obj = self.ss_b_object.currentText() + if silk_obj is not '': + silk_dict['name'] = deepcopy(silk_obj) + silk_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_obj).apertures) + + self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, + args=(silk_dict, + silk_silk_clearance, + _("BOTTOM: Silk to Silk clearance")))) + + if self.ss_t_cb.get_value() is False and self.ss_b_cb.get_value() is False: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Silk to Silk clearance"), + _("At least one Gerber object has to be selected for this rule but none is selected."))) + return + + # RULE: Check Silk to Solder Mask Clearance + if self.clearance_silk2sm_cb.get_value(): + silk_t_dict = dict() + sm_t_dict = dict() + silk_b_dict = dict() + sm_b_dict = dict() + + top_ss = False + bottom_ss = False + top_sm = False + bottom_sm = False + + silk_top = self.ss_t_object.currentText() + if silk_top is not '' and self.ss_t_cb.get_value(): + silk_t_dict['name'] = deepcopy(silk_top) + silk_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures) + top_ss = True + + silk_bottom = self.ss_b_object.currentText() + if silk_bottom is not '' and self.ss_b_cb.get_value(): + silk_b_dict['name'] = deepcopy(silk_bottom) + silk_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures) + bottom_ss = True + + sm_top = self.sm_t_object.currentText() + if sm_top is not '' and self.sm_t_cb.get_value(): + sm_t_dict['name'] = deepcopy(sm_top) + sm_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(sm_top).apertures) + top_sm = True + + sm_bottom = self.sm_b_object.currentText() + if sm_bottom is not '' and self.sm_b_cb.get_value(): + sm_b_dict['name'] = deepcopy(sm_bottom) + sm_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(sm_bottom).apertures) + bottom_sm = True + + try: + silk_sm_clearance = float(self.clearance_silk2sm_entry.get_value()) + except Exception as e: + log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Silk to Solder Mask Clearance"), + _("Value is not valid."))) + return + + if not top_dict and not bottom_dict or not outline_dict: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Silk to Solder Mask Clearance"), + _("One or more of the Gerber objects is not valid."))) + return + + if top_ss is True and top_sm is True: + objs = [silk_t_dict, sm_t_dict] + self.results.append(self.pool.apply_async(self.check_gerber_clearance, + args=(objs, + silk_sm_clearance, + _("TOP: Silk to Solder Mask Clearance")))) + elif bottom_ss is True and bottom_sm is True: + objs = [silk_b_dict, sm_b_dict] + self.results.append(self.pool.apply_async(self.check_gerber_clearance, + args=(objs, + silk_sm_clearance, + _("BOTTOM: Silk to Solder Mask Clearance")))) + else: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Silk to Solder Mask Clearance"), + _("Both Silk and Solder Mask Gerber objects has to be either both Top or both Bottom."))) + return + + # RULE: Check Silk to Outline Clearance + if self.clearance_silk2ol_cb.get_value(): + top_dict = dict() + bottom_dict = dict() + outline_dict = dict() + + silk_top = self.ss_t_object.currentText() + if silk_top is not '' and self.ss_t_cb.get_value(): + top_dict['name'] = deepcopy(silk_top) + top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures) + + silk_bottom = self.ss_b_object.currentText() + if silk_bottom is not '' and self.ss_b_cb.get_value(): + bottom_dict['name'] = deepcopy(silk_bottom) + bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures) + + copper_outline = self.outline_object.currentText() + if copper_outline is not '' and self.out_cb.get_value(): + outline_dict['name'] = deepcopy(copper_outline) + outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures) + + try: + copper_outline_clearance = float(self.clearance_copper2ol_entry.get_value()) + except Exception as e: + log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Silk to Outline Clearance"), + _("Value is not valid."))) + return + + if not top_dict and not bottom_dict or not outline_dict: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Silk to Outline Clearance"), + _("One of the Silk Gerber objects or the Outline Gerber object is not valid."))) + return + + objs = [] + if top_dict: + objs.append(top_dict) + if bottom_dict: + objs.append(bottom_dict) + + if outline_dict: + objs.append(outline_dict) + else: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Silk to Outline Clearance"), + _("Outline Gerber object presence is mandatory for this rule but it is not selected."))) + return + + self.results.append(self.pool.apply_async(self.check_gerber_clearance, + args=(objs, + copper_outline_clearance, + _("Silk to Outline Clearance")))) + + # RULE: Check Minimum Solder Mask Sliver + if self.clearance_silk2silk_cb.get_value(): + sm_dict = dict() + + try: + sm_sm_clearance = float(self.clearance_sm2sm_entry.get_value()) + except Exception as e: + log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Minimum Solder Mask Sliver"), + _("Value is not valid."))) + return + + if self.sm_t_cb.get_value(): + solder_obj = self.sm_t_object.currentText() + if solder_obj is not '': + sm_dict['name'] = deepcopy(solder_obj) + sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures) + + self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, + args=(sm_dict, + sm_sm_clearance, + _("TOP: Minimum Solder Mask Sliver")))) + if self.sm_b_cb.get_value(): + solder_obj = self.sm_b_object.currentText() + if solder_obj is not '': + sm_dict['name'] = deepcopy(solder_obj) + sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures) + + self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, + args=(sm_dict, + sm_sm_clearance, + _("BOTTOM: Minimum Solder Mask Sliver")))) + + if self.sm_t_cb.get_value() is False and self.sm_b_cb.get_value() is False: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Minimum Solder Mask Sliver"), + _("At least one Gerber object has to be selected for this rule but none is selected."))) + return + + # RULE: Check Minimum Annular Ring + if self.ring_integrity_cb.get_value(): + top_dict = dict() + bottom_dict = dict() + exc_dict = dict() + + copper_top = self.copper_t_object.currentText() + if copper_top is not '' and self.copper_t_cb.get_value(): + top_dict['name'] = deepcopy(copper_top) + top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_top).apertures) + + copper_bottom = self.copper_b_object.currentText() + if copper_bottom is not '' and self.copper_b_cb.get_value(): + bottom_dict['name'] = deepcopy(copper_bottom) + bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_bottom).apertures) + + excellon = self.outline_object.currentText() + if excellon is not '' and self.out_cb.get_value(): + exc_dict['name'] = deepcopy(excellon) + exc_dict['apertures'] = deepcopy( + self.app.collection.get_by_name(excellon).tools) + + try: + ring_val = float(self.ring_integrity_entry.get_value()) + except Exception as e: + log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Minimum Annular Ring"), + _("Value is not valid."))) + return + + if not top_dict and not bottom_dict or not exc_dict: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Minimum Annular Ring"), + _("One of the Copper Gerber objects or the Excellon object is not valid."))) + return + + objs = [] + if top_dict: + objs.append(top_dict) + if bottom_dict: + objs.append(bottom_dict) + + if exc_dict: + objs.append(exc_dict) + else: + self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( + _("Minimum Annular Ring"), + _("Excellon object presence is mandatory for this rule but it is not selected."))) + return + + self.results.append(self.pool.apply_async(self.check_gerber_annular_ring, + args=(objs, + ring_val, + _("Minimum Annular Ring")))) # RULE: Check Hole to Hole Clearance if self.clearance_d2d_cb.get_value():