diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 71904f77..14f7d04b 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -144,8 +144,8 @@ class App(QtCore.QObject): # Manual URL manual_url = "http://flatcam.org/manual/index.html" video_url = "https://www.youtube.com/playlist?list=PLVvP2SYRpx-AQgNlfoxw93tXUXon7G94_" - gerber_spec_url ="https://www.ucamco.com/files/downloads/file/81/The_Gerber_File_Format_specification." \ - "pdf?7ac957791daba2cdf4c2c913f67a43da" + gerber_spec_url = "https://www.ucamco.com/files/downloads/file/81/The_Gerber_File_Format_specification." \ + "pdf?7ac957791daba2cdf4c2c913f67a43da" excellon_spec_url = "https://www.ucamco.com/files/downloads/file/305/the_xnc_file_format_specification.pdf" bug_report_url = "https://bitbucket.org/jpcgt/flatcam/issues?status=new&status=open" @@ -2420,7 +2420,7 @@ class App(QtCore.QObject): self.move_tool = None self.cutout_tool = None self.ncclear_tool = None - self.optimal_tool=None + self.optimal_tool = None self.paint_tool = None self.transform_tool = None self.properties_tool = None @@ -2561,6 +2561,9 @@ class App(QtCore.QObject): # set the value used in the Windows Title self.engine = self.ui.general_defaults_form.general_app_group.ge_radio.get_value() + # this will hold the TCL instance + self.tcl = None + # ############################################################################### # ############# Save defaults to factory_defaults.FlatConfig file ############### # ############# It's done only once after install ############### @@ -2859,7 +2862,7 @@ class App(QtCore.QObject): try: self.trayIcon.hide() except Exception as e: - pass + pass fcTranslate.restart_program(app=self) @@ -4719,22 +4722,21 @@ class App(QtCore.QObject): f = open(data_path + "/current_defaults.FlatConfig", "w") json.dump(defaults, f, default=to_dict, indent=2, sort_keys=True) f.close() - except: - self.inform.emit('[ERROR_NOTCL] %s' % - _("Failed to write defaults to file.")) + except Exception as e: + log.debug("App.save_defaults() --> %s" % str(e)) + self.inform.emit(f'[ERROR_NOTCL] {_("Failed to write defaults to file.")}') return if not silent: - self.inform.emit('[success] %s' % - _("Preferences saved.")) + self.inform.emit(f'[success] {_("Preferences saved.")}') - def save_factory_defaults(self, silent=False, data_path=None): + def save_factory_defaults(self, silent_message=False, data_path=None): """ Saves application factory default options ``self.defaults`` to factory_defaults.FlatConfig. It's a one time job done just after the first install. - :param silent: whether to display a message in status bar or not; boolean + :param silent_message: whether to display a message in status bar or not; boolean :param data_path: the path where to save the default preferences file (factory_defaults.FlatConfig) When the application is portable it should be a mobile location. :return: None @@ -4753,8 +4755,7 @@ class App(QtCore.QObject): e = sys.exc_info()[0] App.log.error("Could not load factory defaults file.") App.log.error(str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % - _("Could not load factory defaults file.")) + self.inform.emit(f'[ERROR_NOTCL] {_("Could not load factory defaults file.")}') return try: @@ -4763,8 +4764,7 @@ class App(QtCore.QObject): e = sys.exc_info()[0] App.log.error("Failed to parse factory defaults file.") App.log.error(str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % - _("Failed to parse factory defaults file.")) + self.inform.emit(f'[ERROR_NOTCL] {_("Failed to parse factory defaults file.")}') return # Update options @@ -4777,13 +4777,13 @@ class App(QtCore.QObject): f_f_def_s = open(data_path + "/factory_defaults.FlatConfig", "w") json.dump(factory_defaults, f_f_def_s, default=to_dict, indent=2, sort_keys=True) f_f_def_s.close() - except: - self.inform.emit('[ERROR_NOTCL] %s' % - _("Failed to write factory defaults to file.")) + except Exception as e: + log.debug(f"App.save_factory_default() save update --> {str(e)}") + self.inform.emit(f'[ERROR_NOTCL] {_("Failed to write factory defaults to file.")}') return - if silent is False: - self.inform.emit(_("Factory defaults saved.")) + if silent_message is False: + self.inform.emit(f'{_("Factory defaults saved.")}') def final_save(self): """ @@ -4793,8 +4793,7 @@ class App(QtCore.QObject): :return: None """ if self.save_in_progress: - self.inform.emit('[WARNING_NOTCL] %s' % - _("Application is saving the project. Please wait ...")) + self.inform.emit(f'[WARNING_NOTCL] {_("Application is saving the project. Please wait ...")}') return if self.should_we_save and self.collection.get_list(): diff --git a/README.md b/README.md index 263b9f84..8d3f00c2 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +9.10.2019 + +- updated the Rules Check Tool - solved some issues + 8.10.2019 - modified the FCSpinner and FCDoubleSpinner GUI elements such that the wheel event will not change the values inside unless there is a focus in the lineedit of the SpinBox diff --git a/flatcamTools/ToolRulesCheck.py b/flatcamTools/ToolRulesCheck.py index 0d24e371..963d356c 100644 --- a/flatcamTools/ToolRulesCheck.py +++ b/flatcamTools/ToolRulesCheck.py @@ -489,6 +489,8 @@ class RulesCheck(FlatCAMTool): self.pool = self.app.pool self.results = None + self.decimals = 4 + # def on_object_loaded(self, index, row): # print(index.internalPointer().child_items[row].obj.options['name'], index.data()) @@ -574,20 +576,37 @@ class RulesCheck(FlatCAMTool): if not gerber_obj: return 'Fail. Not enough Gerber objects to check Gerber 2 Gerber clearance' - total_geo = list() + obj_violations['name'] = gerber_obj['name'] + + solid_geo = list() + clear_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']) + solid_geo.append(geo_el['solid']) + if 'clear' in geo_el and geo_el['clear'] is not None: + clear_geo.append(geo_el['clear']) - total_geo = MultiPolygon(total_geo) - total_geo = total_geo.buffer(0) + if clear_geo: + total_geo = list() + for geo_c in clear_geo: + for geo_s in solid_geo: + if geo_c.within(geo_s): + total_geo.append(geo_s.difference(geo_c)) + else: + total_geo = MultiPolygon(solid_geo) + total_geo = total_geo.buffer(0.000001) - iterations = len(total_geo) - iterations = (iterations * (iterations - 1)) / 2 + if isinstance(total_geo, Polygon): + iterations = 1 + obj_violations['points'] =['Failed. Only one polygon.'] + return rule_title, [obj_violations] + else: + iterations = len(total_geo) + iterations = (iterations * (iterations - 1)) / 2 log.debug("RulesCheck.check_gerber_clearance(). Iterations: %s" % str(iterations)) min_dict = dict() @@ -608,14 +627,12 @@ class RulesCheck(FlatCAMTool): else: min_dict[dist] = [loc] idx += 1 - - points_list = list() + points_list = set() for dist in min_dict.keys(): for location in min_dict[dist]: - points_list.append(location) + points_list.add(location) - obj_violations['name'] = gerber_obj['name'] - obj_violations['points'] = points_list + obj_violations['points'] = list(points_list) violations.append(deepcopy(obj_violations)) return rule_title, violations @@ -675,7 +692,17 @@ class RulesCheck(FlatCAMTool): total_geo_grb_3 = MultiPolygon(total_geo_grb_3) total_geo_grb_3 = total_geo_grb_3.buffer(0) - iterations = len(total_geo_grb_1) * len(total_geo_grb_3) + if isinstance(total_geo_grb_1, Polygon): + len_1 = 1 + else: + len_1 = len(total_geo_grb_1) + + if isinstance(total_geo_grb_3, Polygon): + len_3 = 1 + else: + len_3 = len(total_geo_grb_3) + + iterations = len_1 * len_3 log.debug("RulesCheck.check_gerber_clearance(). Iterations: %s" % str(iterations)) min_dict = dict() @@ -905,7 +932,17 @@ class RulesCheck(FlatCAMTool): for geo in geometry: total_geo_exc.append(geo) - iterations = len(total_geo_grb) * len(total_geo_exc) + if isinstance(total_geo_grb, Polygon): + len_1 = 1 + else: + len_1 = len(total_geo_grb) + + if isinstance(total_geo_exc, Polygon): + len_2 = 1 + else: + len_2 = len(total_geo_exc) + + iterations = len_1 * len_2 log.debug("RulesCheck.check_gerber_annular_ring(). Iterations: %s" % str(iterations)) min_dict = dict() @@ -976,7 +1013,6 @@ 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()) @@ -988,25 +1024,28 @@ class RulesCheck(FlatCAMTool): 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) + copper_t_obj = self.copper_t_object.currentText() + copper_t_dict = dict() + + if copper_t_obj is not '': + copper_t_dict['name'] = deepcopy(copper_t_obj) + copper_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_t_obj).apertures) self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, - args=(copper_dict, + args=(copper_t_dict, copper_copper_clearance, - _("TOP: Copper to 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) + copper_b_obj = self.copper_b_object.currentText() + copper_b_dict = dict() + if copper_b_obj is not '': + copper_b_dict['name'] = deepcopy(copper_b_obj) + copper_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_b_obj).apertures) self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, - args=(copper_dict, + args=(copper_b_dict, copper_copper_clearance, - _("BOTTOM: Copper to 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' % ( @@ -1090,7 +1129,7 @@ class RulesCheck(FlatCAMTool): self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, args=(silk_dict, silk_silk_clearance, - _("TOP: Silk to 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 '': @@ -1100,7 +1139,7 @@ class RulesCheck(FlatCAMTool): self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, args=(silk_dict, silk_silk_clearance, - _("BOTTOM: Silk to 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' % ( @@ -1164,13 +1203,13 @@ class RulesCheck(FlatCAMTool): self.results.append(self.pool.apply_async(self.check_gerber_clearance, args=(objs, silk_sm_clearance, - _("TOP: Silk to Solder Mask 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")))) + _("BOTTOM -> Silk to Solder Mask Clearance")))) else: self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( _("Silk to Solder Mask Clearance"), @@ -1254,7 +1293,7 @@ class RulesCheck(FlatCAMTool): self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, args=(sm_dict, sm_sm_clearance, - _("TOP: Minimum Solder Mask Sliver")))) + _("TOP -> Minimum Solder Mask Sliver")))) if self.sm_b_cb.get_value(): solder_obj = self.sm_b_object.currentText() if solder_obj is not '': @@ -1264,7 +1303,7 @@ class RulesCheck(FlatCAMTool): self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance, args=(sm_dict, sm_sm_clearance, - _("BOTTOM: Minimum Solder Mask Sliver")))) + _("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' % ( @@ -1391,29 +1430,31 @@ class RulesCheck(FlatCAMTool): def init(new_obj, app_obj): txt = '' for el in res: - txt += 'RULE NAME:\t%s\n' % str(el[0]).upper() + txt += 'RULE NAME:    %s
' % str(el[0]).upper() if isinstance(el[1][0]['name'], list): for name in el[1][0]['name']: - txt += 'File name: %s\n' % str(name) - else: - txt += 'File name: %s\n' % str(el[1][0]['name']) + txt += 'File name: %s
' % str(name) + else: + txt += 'File name: %s
' % str(el[1][0]['name']) point_txt = '' if el[1][0]['points']: - txt += '{title}: {status}.\n'.format(title=_("STATUS"), - color='red', - status=_("FAILED")) - - for pt in el[1][0]['points']: - point_txt += str(pt) - point_txt += ', ' - txt += 'Violations: %s\n' % str(point_txt) + txt += '{title}: {status}.
'.format(title=_("STATUS"), + color='red', + status=_("FAILED")) + if 'Failed' in el[1][0]['points'][0]: + point_txt = el[1][0]['points'][0] + else: + for pt in el[1][0]['points']: + point_txt += '(%.*f, %.*f)' % (self.decimals, float(pt[0]), self.decimals, float(pt[1])) + point_txt += ', ' + txt += 'Violations: %s
' % str(point_txt) else: - txt += '{title}: {status}.\n'.format(title=_("STATUS"), - color='green', - status=_("PASSED")) - txt += '%s\n' % _("Violations: There are no violations for the current rule.") - txt += '\n\n' + txt += '{title}: {status}.
'.format(title=_("STATUS"), + color='green', + status=_("PASSED")) + txt += '%s
' % _("Violations: There are no violations for the current rule.") + txt += '

' new_obj.source_file = txt new_obj.read_only = True