diff --git a/AppGUI/GUIElements.py b/AppGUI/GUIElements.py index 97fe2b6a..0264cf64 100644 --- a/AppGUI/GUIElements.py +++ b/AppGUI/GUIElements.py @@ -573,6 +573,7 @@ class EvalEntry(QtWidgets.QLineEdit): def __init__(self, parent=None): super(EvalEntry, self).__init__(parent) self.readyToEdit = True + self.editingFinished.connect(self.on_edit_finished) def on_edit_finished(self): @@ -599,7 +600,6 @@ class EvalEntry(QtWidgets.QLineEdit): def get_value(self): raw = str(self.text()).strip(' ') - evaled = 0.0 try: evaled = eval(raw) except Exception as e: @@ -656,6 +656,17 @@ class EvalEntry2(QtWidgets.QLineEdit): return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height()) +class NumericalEvalEntry(EvalEntry): + """ + Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,% + """ + def __init__(self): + super().__init__() + + regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s]*") + validator = QtGui.QRegExpValidator(regex, self) + self.setValidator(validator) + class FCSpinner(QtWidgets.QSpinBox): returnPressed = QtCore.pyqtSignal() diff --git a/AppGUI/MainGUI.py b/AppGUI/MainGUI.py index cec8ede0..eec4fd42 100644 --- a/AppGUI/MainGUI.py +++ b/AppGUI/MainGUI.py @@ -855,7 +855,7 @@ class MainGUI(QtWidgets.QMainWindow): self.copy_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/copy_file32.png'), _("Copy")) self.delete_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/delete_file32.png'), _("&Delete")) + QtGui.QIcon(self.app.resource_location + '/trash32.png'), _("&Delete")) self.toolbaredit.addSeparator() self.distance_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/distance32.png'), _("Distance Tool")) @@ -1851,7 +1851,7 @@ class MainGUI(QtWidgets.QMainWindow): self.copy_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/copy_file32.png'), _("Copy")) self.delete_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/delete_file32.png'), _("&Delete")) + QtGui.QIcon(self.app.resource_location + '/trash32.png'), _("&Delete")) self.toolbaredit.addSeparator() self.distance_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/distance32.png'), _("Distance Tool")) diff --git a/AppObjects/FlatCAMExcellon.py b/AppObjects/FlatCAMExcellon.py index c0250871..262ba293 100644 --- a/AppObjects/FlatCAMExcellon.py +++ b/AppObjects/FlatCAMExcellon.py @@ -31,7 +31,7 @@ if '_' not in builtins.__dict__: class ExcellonObject(FlatCAMObj, Excellon): """ - Represents Excellon/Drill code. + Represents Excellon/Drill code. An object stored in the FlatCAM objects collection (a dict) """ ui_type = ExcellonObjectUI @@ -146,9 +146,11 @@ class ExcellonObject(FlatCAMObj, Excellon): If only one object is in exc_list parameter then this function will copy that object in the exc_final - :param exc_list: List or one object of ExcellonObject Objects to join. - :param exc_final: Destination ExcellonObject object. - :return: None + :param exc_list: List or one object of ExcellonObject Objects to join. + :type exc_list: list + :param exc_final: Destination ExcellonObject object. + :type exc_final: class + :return: None """ if decimals is None: @@ -316,6 +318,12 @@ class ExcellonObject(FlatCAMObj, Excellon): exc_final.create_geometry() def build_ui(self): + """ + Will (re)build the Excellon UI updating it (the tool table) + + :return: None + :rtype: + """ FlatCAMObj.build_ui(self) # Area Exception - exclusion shape added signal @@ -586,9 +594,9 @@ class ExcellonObject(FlatCAMObj, Excellon): Configures the user interface for this object. Connects options to form fields. - :param ui: User interface object. - :type ui: ExcellonObjectUI - :return: None + :param ui: User interface object. + :type ui: ExcellonObjectUI + :return: None """ FlatCAMObj.set_ui(self, ui) @@ -729,6 +737,12 @@ class ExcellonObject(FlatCAMObj, Excellon): self.ui.operation_radio.setEnabled(False) def ui_connect(self): + """ + Will connect all signals in the Excellon UI that needs to be connected + + :return: None + :rtype: + """ # selective plotting for row in range(self.ui.tools_table.rowCount() - 2): @@ -751,6 +765,12 @@ class ExcellonObject(FlatCAMObj, Excellon): current_widget.returnPressed.connect(self.form_to_storage) def ui_disconnect(self): + """ + Will disconnect all signals in the Excellon UI that needs to be disconnected + + :return: None + :rtype: + """ # selective plotting for row in range(self.ui.tools_table.rowCount()): try: @@ -793,6 +813,12 @@ class ExcellonObject(FlatCAMObj, Excellon): pass def on_row_selection_change(self): + """ + Called when the user clicks on a row in Tools Table + + :return: None + :rtype: + """ self.ui_disconnect() sel_rows = [] @@ -843,6 +869,14 @@ class ExcellonObject(FlatCAMObj, Excellon): self.ui_connect() def storage_to_form(self, dict_storage): + """ + Will update the GUI with data from the "storage" in this case the dict self.tools + + :param dict_storage: A dictionary holding the data relevant for gnerating Gcode from Excellon + :type dict_storage: dict + :return: None + :rtype: + """ for form_key in self.form_fields: for storage_key in dict_storage: if form_key == storage_key and form_key not in \ @@ -854,6 +888,12 @@ class ExcellonObject(FlatCAMObj, Excellon): pass def form_to_storage(self): + """ + Will update the 'storage' attribute which is the dict self.tools with data collected from GUI + + :return: None + :rtype: + """ if self.ui.tools_table.rowCount() == 0: # there is no tool in tool table so we can't save the GUI elements values to storage return @@ -882,6 +922,14 @@ class ExcellonObject(FlatCAMObj, Excellon): self.ui_connect() def on_operation_type(self, val): + """ + Called by a RadioSet activated_custom signal + + :param val: Parameter passes by the signal that called this method + :type val: str + :return: None + :rtype: + """ if val == 'mill': self.ui.mill_type_label.show() self.ui.milling_type_radio.show() @@ -912,8 +960,8 @@ class ExcellonObject(FlatCAMObj, Excellon): Returns the keys to the self.tools dictionary corresponding to the selections on the tool list in the AppGUI. - :return: List of tools. - :rtype: list + :return: List of tools. + :rtype: list """ return [str(x.text()) for x in self.ui.tools_table.selectedItems()] @@ -922,8 +970,8 @@ class ExcellonObject(FlatCAMObj, Excellon): """ Returns a list of lists, each list in the list is made out of row elements - :return: List of table_tools items. - :rtype: list + :return: List of table_tools items. + :rtype: list """ table_tools_items = [] for x in self.ui.tools_table.selectedItems(): @@ -951,7 +999,21 @@ class ExcellonObject(FlatCAMObj, Excellon): def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1, slot_type='routing'): """ Returns two values, first is a boolean , if 1 then the file has slots and second contain the Excellon code - :return: has_slots and Excellon_code + + :param whole: Integer part digits + :type whole: int + :param fract: Fractional part digits + :type fract: int + :param e_zeros: Excellon zeros suppression: LZ or TZ + :type e_zeros: str + :param form: Excellon format: 'dec', + :type form: str + :param factor: Conversion factor + :type factor: float + :param slot_type: How to treat slots: "routing" or "drilling" + :type slot_type: str + :return: A tuple: (has_slots, Excellon_code) -> (bool, str) + :rtype: tuple """ excellon_code = '' @@ -1123,8 +1185,8 @@ class ExcellonObject(FlatCAMObj, Excellon): object's options and returns a (success, msg) tuple as feedback for shell operations. - :return: Success/failure condition tuple (bool, str). - :rtype: tuple + :return: Success/failure condition tuple (bool, str). + :rtype: tuple """ # Get the tools from the list. These are keys @@ -1167,6 +1229,15 @@ class ExcellonObject(FlatCAMObj, Excellon): return False, "Error: Milling tool is larger than hole." def geo_init(geo_obj, app_obj): + """ + + :param geo_obj: New object + :type geo_obj: GeometryObject + :param app_obj: App + :type app_obj: FlatCAMApp.App + :return: + :rtype: + """ assert geo_obj.kind == 'geometry', "Initializer expected a GeometryObject, got %s" % type(geo_obj) # ## Add properties to the object diff --git a/AppObjects/FlatCAMGerber.py b/AppObjects/FlatCAMGerber.py index 8758d6dc..0b9552e7 100644 --- a/AppObjects/FlatCAMGerber.py +++ b/AppObjects/FlatCAMGerber.py @@ -896,7 +896,7 @@ class GerberObject(FlatCAMObj, Gerber): }) for nr_pass in range(passes): - iso_offset = dia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * dia) + iso_offset = dia * ((2 * nr_pass + 1) / 2.0000001) - (nr_pass * overlap * dia) # if milling type is climb then the move is counter-clockwise around features mill_dir = 1 if milling_type == 'cl' else 0 @@ -945,8 +945,7 @@ class GerberObject(FlatCAMObj, Gerber): self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot) else: for i in range(passes): - - offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia) + offset = dia * ((2 * i + 1) / 2.0000001) - (i * overlap * dia) if passes > 1: if outname is None: if self.iso_type == 0: diff --git a/AppTools/ToolEtchCompensation.py b/AppTools/ToolEtchCompensation.py index c4c15565..57f6bfb6 100644 --- a/AppTools/ToolEtchCompensation.py +++ b/AppTools/ToolEtchCompensation.py @@ -8,9 +8,9 @@ from PyQt5 import QtWidgets, QtCore from AppTool import AppTool -from AppGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox +from AppGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox, NumericalEvalEntry -from shapely.geometry import box +from shapely.ops import unary_union from copy import deepcopy @@ -95,8 +95,8 @@ class ToolEtchCompensation(AppTool): self.thick_entry.set_range(0.0000, 9999.9999) self.thick_entry.setObjectName(_("Thickness")) - grid0.addWidget(self.thick_label, 5, 0, 1, 2) - grid0.addWidget(self.thick_entry, 6, 0, 1, 2) + grid0.addWidget(self.thick_label, 5, 0) + grid0.addWidget(self.thick_entry, 5, 1) self.ratio_label = QtWidgets.QLabel('%s:' % _("Ratio")) self.ratio_label.setToolTip( @@ -106,17 +106,48 @@ class ToolEtchCompensation(AppTool): "- preselection -> value which depends on a selection of etchants") ) self.ratio_radio = RadioSet([ - {'label': _('PreSelection'), 'value': 'p'}, - {'label': _('Custom'), 'value': 'c'} - ]) + {'label': _('Custom'), 'value': 'c'}, + {'label': _('PreSelection'), 'value': 'p'} + ], orientation='vertical', stretch=False) grid0.addWidget(self.ratio_label, 7, 0, 1, 2) grid0.addWidget(self.ratio_radio, 8, 0, 1, 2) + # Etchants + self.etchants_label = QtWidgets.QLabel('%s:' % _('Etchants')) + self.etchants_label.setToolTip( + _("A list of etchants.") + ) + self.etchants_combo = FCComboBox(callback=self.confirmation_message) + self.etchants_combo.setObjectName(_("Etchants")) + self.etchants_combo.addItems(["CuCl2", "FeCl3"]) + + grid0.addWidget(self.etchants_label, 9, 0) + grid0.addWidget(self.etchants_combo, 9, 1) + + # Etch Factor + self.factor_label = QtWidgets.QLabel('%s:' % _('Etch factor')) + self.factor_label.setToolTip( + _("The ratio between depth etch and lateral etch .\n" + "Accepts real numbers and formulas using the operators: /,*,+,-,%") + ) + self.factor_entry = NumericalEvalEntry() + self.factor_entry.setPlaceholderText(_("Real number or formula")) + self.factor_entry.setObjectName(_("Etch_factor")) + + # Hide the Etchants and Etch factor + self.etchants_label.hide() + self.etchants_combo.hide() + self.factor_label.hide() + self.factor_entry.hide() + + grid0.addWidget(self.factor_label, 10, 0) + grid0.addWidget(self.factor_entry, 10, 1) + separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.HLine) separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 9, 0, 1, 2) + grid0.addWidget(separator_line, 13, 0, 1, 2) self.compensate_btn = FCButton(_('Compensate')) self.compensate_btn.setToolTip( @@ -128,7 +159,7 @@ class ToolEtchCompensation(AppTool): font-weight: bold; } """) - grid0.addWidget(self.compensate_btn, 10, 0, 1, 2) + grid0.addWidget(self.compensate_btn, 14, 0, 1, 2) self.tools_box.addStretch() @@ -145,7 +176,7 @@ class ToolEtchCompensation(AppTool): """) self.tools_box.addWidget(self.reset_button) - self.compensate_btn.clicked.connect(self.on_grb_invert) + self.compensate_btn.clicked.connect(self.on_compensate) self.reset_button.clicked.connect(self.set_tool_ui) self.ratio_radio.activated_custom.connect(self.on_ratio_change) @@ -153,8 +184,8 @@ class ToolEtchCompensation(AppTool): AppTool.install(self, icon, separator, shortcut='', **kwargs) def run(self, toggle=True): - self.app.defaults.report_usage("ToolInvertGerber()") - log.debug("ToolInvertGerber() is running ...") + self.app.defaults.report_usage("ToolEtchCompensation()") + log.debug("ToolEtchCompensation() is running ...") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -178,28 +209,40 @@ class ToolEtchCompensation(AppTool): AppTool.run(self) self.set_tool_ui() - self.app.ui.notebook.setTabText(2, _("Invert Tool")) + self.app.ui.notebook.setTabText(2, _("Etch Compensation Tool")) def set_tool_ui(self): - self.thick_entry.set_value(18) - self.ratio_radio.set_value('p') + self.thick_entry.set_value(18.0) + self.ratio_radio.set_value('c') def on_ratio_change(self, val): - pass + """ + Called on activated_custom signal of the RadioSet GUI element self.radio_ratio - def on_grb_invert(self): - margin = self.margin_entry.get_value() - if round(margin, self.decimals) == 0.0: - margin = 1E-10 + :param val: 'c' or 'p': 'c' means custom factor and 'p' means preselected etchants + :type val: str + :return: None + :rtype: + """ + if val == 'c': + self.etchants_label.hide() + self.etchants_combo.hide() + self.factor_label.show() + self.factor_entry.show() + else: + self.etchants_label.show() + self.etchants_combo.show() + self.factor_label.hide() + self.factor_entry.hide() - join_style = {'r': 1, 'b': 3, 's': 2}[self.join_radio.get_value()] - if join_style is None: - join_style = 'r' + def on_compensate(self): + ratio_type = self.ratio_radio.get_value() + thickness = self.thick_entry.get_value() / 1000 # in microns grb_circle_steps = int(self.app.defaults["gerber_circle_steps"]) obj_name = self.gerber_combo.currentText() - outname = obj_name + "_inverted" + outname = obj_name + "_comp" # Get source object. try: @@ -214,74 +257,51 @@ class ToolEtchCompensation(AppTool): self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name))) return - xmin, ymin, xmax, ymax = grb_obj.bounds() - - grb_box = box(xmin, ymin, xmax, ymax).buffer(margin, resolution=grb_circle_steps, join_style=join_style) + if ratio_type == 'c': + etch_factor = 1 / self.factor_entry.get_value() + else: + etchant = self.etchants_combo.get_value() + if etchant == "CuCl2": + etch_factor = 0.33 + else: + etch_factor = 0.25 + offset = thickness / etch_factor try: __ = iter(grb_obj.solid_geometry) except TypeError: grb_obj.solid_geometry = list(grb_obj.solid_geometry) - new_solid_geometry = deepcopy(grb_box) + new_solid_geometry = [] for poly in grb_obj.solid_geometry: - new_solid_geometry = new_solid_geometry.difference(poly) + new_solid_geometry.append(poly.buffer(offset, int(grb_circle_steps))) + new_solid_geometry = unary_union(new_solid_geometry) new_options = {} for opt in grb_obj.options: new_options[opt] = deepcopy(grb_obj.options[opt]) - new_apertures = {} + new_apertures = deepcopy(grb_obj.apertures) - # for apid, val in grb_obj.apertures.items(): - # new_apertures[apid] = {} - # for key in val: - # if key == 'geometry': - # new_apertures[apid]['geometry'] = [] - # for elem in val['geometry']: - # geo_elem = {} - # if 'follow' in elem: - # try: - # geo_elem['clear'] = elem['follow'].buffer(val['size'] / 2.0).exterior - # except AttributeError: - # # TODO should test if width or height is bigger - # geo_elem['clear'] = elem['follow'].buffer(val['width'] / 2.0).exterior - # if 'clear' in elem: - # if isinstance(elem['clear'], Polygon): - # try: - # geo_elem['solid'] = elem['clear'].buffer(val['size'] / 2.0, grb_circle_steps) - # except AttributeError: - # # TODO should test if width or height is bigger - # geo_elem['solid'] = elem['clear'].buffer(val['width'] / 2.0, grb_circle_steps) - # else: - # geo_elem['follow'] = elem['clear'] - # new_apertures[apid]['geometry'].append(deepcopy(geo_elem)) - # else: - # new_apertures[apid][key] = deepcopy(val[key]) - - if '0' not in new_apertures: - new_apertures['0'] = {} - new_apertures['0']['type'] = 'C' - new_apertures['0']['size'] = 0.0 - new_apertures['0']['geometry'] = [] - - try: - for poly in new_solid_geometry: - new_el = {} - new_el['solid'] = poly - new_el['follow'] = poly.exterior - new_apertures['0']['geometry'].append(new_el) - except TypeError: - new_el = {} - new_el['solid'] = new_solid_geometry - new_el['follow'] = new_solid_geometry.exterior - new_apertures['0']['geometry'].append(new_el) - - for td in new_apertures: - print(td, new_apertures[td]) + for ap in new_apertures: + for k in ap: + if k == 'geometry': + for geo_el in new_apertures[ap]['geometry']: + if 'solid' in geo_el: + geo_el['solid'] = geo_el['solid'].buffer(offset, int(grb_circle_steps)) def init_func(new_obj, app_obj): + """ + Init a new object in FlatCAM Object collection + + :param new_obj: New object + :type new_obj: ObjectCollection + :param app_obj: App + :type app_obj: App_Main.App + :return: None + :rtype: + """ new_obj.options.update(new_options) new_obj.options['name'] = outname new_obj.fill_color = deepcopy(grb_obj.fill_color) diff --git a/AppTools/ToolInvertGerber.py b/AppTools/ToolInvertGerber.py index 6acc013b..50579a93 100644 --- a/AppTools/ToolInvertGerber.py +++ b/AppTools/ToolInvertGerber.py @@ -278,9 +278,6 @@ class ToolInvertGerber(AppTool): new_el['follow'] = new_solid_geometry.exterior new_apertures['0']['geometry'].append(new_el) - for td in new_apertures: - print(td, new_apertures[td]) - def init_func(new_obj, app_obj): new_obj.options.update(new_options) new_obj.options['name'] = outname diff --git a/App_Main.py b/App_Main.py index dc72594a..f5202d02 100644 --- a/App_Main.py +++ b/App_Main.py @@ -42,7 +42,7 @@ import socket # ################################### Imports part of FlatCAM ############################################# # #################################################################################################################### -# Diverse +# Various from Common import LoudDict from Common import color_variant from Common import ExclusionAreas @@ -53,8 +53,10 @@ from AppDatabase import ToolsDB2 from vispy.gloo.util import _screenshot from vispy.io import write_png -# FlatCAM Objects +# FlatCAM defaults (preferences) from defaults import FlatCAMDefaults + +# FlatCAM Objects from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI from AppGUI.preferences.PreferencesUIManager import PreferencesUIManager from AppObjects.ObjectCollection import * @@ -105,7 +107,7 @@ if '_' not in builtins.__dict__: class App(QtCore.QObject): """ - The main application class. The constructor starts the AppGUI. + The main application class. The constructor starts the GUI and all other classes used by the program. """ # ############################################################################################################### @@ -289,7 +291,7 @@ class App(QtCore.QObject): self.new_launch.start.emit() # ############################################################################################################ - # # ######################################## OS-specific ##################################################### + # ########################################## OS-specific ##################################################### # ############################################################################################################ portable = False @@ -401,13 +403,12 @@ class App(QtCore.QObject): json.dump([], fp) fp.close() - # Application directory. CHDIR to it. Otherwise, trying to load - # GUI icons will fail as their path is relative. + # Application directory. CHDIR to it. Otherwise, trying to load GUI icons will fail as their path is relative. # This will fail under cx_freeze ... self.app_home = os.path.dirname(os.path.realpath(__file__)) - App.log.debug("Application path is " + self.app_home) - App.log.debug("Started in " + os.getcwd()) + log.debug("Application path is " + self.app_home) + log.debug("Started in " + os.getcwd()) # cx_freeze workaround if os.path.isfile(self.app_home): @@ -451,7 +452,6 @@ class App(QtCore.QObject): # ########################################################################################################### # ###################################### Setting the Splash Screen ########################################## # ########################################################################################################### - splash_settings = QSettings("Open Source", "FlatCAM") if splash_settings.contains("splash_screen"): show_splash = splash_settings.value("splash_screen") @@ -1923,7 +1923,7 @@ class App(QtCore.QObject): self.corners_tool.install(icon=QtGui.QIcon(self.resource_location + '/corners_32.png'), pos=self.ui.menutool) self.etch_tool = ToolEtchCompensation(self) - self.etch_tool.install(icon=QtGui.QIcon(self.resource_location + '/etcg_32.png'), pos=self.ui.menutool) + self.etch_tool.install(icon=QtGui.QIcon(self.resource_location + '/etch_32.png'), pos=self.ui.menutool) self.transform_tool = ToolTransform(self) self.transform_tool.install(icon=QtGui.QIcon(self.resource_location + '/transform.png'), @@ -4811,6 +4811,16 @@ class App(QtCore.QObject): self.defaults.report_usage("on_copy_command()") def initialize(obj_init, app): + """ + + :param obj_init: the new object + :type obj_init: class + :param app: An instance of the App class + :type app: App + :return: None + :rtype: + """ + obj_init.solid_geometry = deepcopy(obj.solid_geometry) try: obj_init.follow_geometry = deepcopy(obj.follow_geometry) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ccc48c..26af1127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ CHANGELOG for FlatCAM beta ================================================= +24.05.2020 + +- changes some icons +- added a new GUI element which is a evaluated LineEdit that accepts only float numbers and /,*,+,-,% chars +- finished the Etch Compensation Tool + 23.05.2020 - fixed a issue when testing for Exclusion areas overlap over the Geometry object solid_geometry diff --git a/Common.py b/Common.py index 7887954b..9d89b66b 100644 --- a/Common.py +++ b/Common.py @@ -12,7 +12,7 @@ # ########################################################## from PyQt5 import QtCore -from shapely.geometry import Polygon, MultiPolygon, Point, LineString +from shapely.geometry import Polygon, Point, LineString from shapely.ops import unary_union from AppGUI.VisPyVisuals import ShapeCollection @@ -32,7 +32,9 @@ if '_' not in builtins.__dict__: class GracefulException(Exception): - # Graceful Exception raised when the user is requesting to cancel the current threaded task + """ + Graceful Exception raised when the user is requesting to cancel the current threaded task + """ def __init__(self): super().__init__() @@ -107,8 +109,11 @@ def color_variant(hex_color, bright_factor=1): Takes a color in HEX format #FF00FF and produces a lighter or darker variant :param hex_color: color to change - :param bright_factor: factor to change the color brightness [0 ... 1] - :return: modified color + :type hex_color: str + :param bright_factor: factor to change the color brightness [0 ... 1] + :type bright_factor: float + :return: Modified color + :rtype: str """ if len(hex_color) != 7: @@ -133,7 +138,9 @@ def color_variant(hex_color, bright_factor=1): class ExclusionAreas(QtCore.QObject): - + """ + Functionality for adding Exclusion Areas for the Excellon and Geometry FlatCAM Objects + """ e_shape_modified = QtCore.pyqtSignal() def __init__(self, app): @@ -230,6 +237,14 @@ class ExclusionAreas(QtCore.QObject): # To be called after clicking on the plot. def on_mouse_release(self, event): + """ + Called on mouse click release. + + :param event: Mouse event + :type event: + :return: None + :rtype: + """ if self.app.is_legacy is False: event_pos = event.pos # event_is_dragging = event.is_dragging @@ -417,6 +432,13 @@ class ExclusionAreas(QtCore.QObject): self.e_shape_modified.emit() def area_disconnect(self): + """ + Will do the cleanup. Will disconnect the mouse events for the custom handlers in this class and initialize + certain class attributes. + + :return: None + :rtype: + """ if self.app.is_legacy is False: self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release) self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move) @@ -441,8 +463,15 @@ class ExclusionAreas(QtCore.QObject): self.app.call_source = "app" self.app.inform.emit("[WARNING_NOTCL] %s" % _("Cancelled. Area exclusion drawing was interrupted.")) - # called on mouse move def on_mouse_move(self, event): + """ + Called on mouse move + + :param event: mouse event + :type event: + :return: None + :rtype: + """ shape_type = self.shape_type_button.get_value() if self.app.is_legacy is False: @@ -513,6 +542,12 @@ class ExclusionAreas(QtCore.QObject): data=(curr_pos[0], curr_pos[1])) def on_clear_area_click(self): + """ + Slot for clicking the button for Deleting all the Exclusion areas. + + :return: None + :rtype: + """ self.clear_shapes() # restore the default StyleSheet @@ -527,6 +562,12 @@ class ExclusionAreas(QtCore.QObject): self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object.")) def clear_shapes(self): + """ + Will delete all the Exclusion areas; will delete on canvas any possible selection box for the Exclusion areas. + + :return: None + :rtype: + """ self.exclusion_areas_storage.clear() AppTool.delete_moving_selection_shape(self) self.app.delete_selection_shape() @@ -536,8 +577,9 @@ class ExclusionAreas(QtCore.QObject): def delete_sel_shapes(self, idxs): """ - :param idxs: list of indexes in self.exclusion_areas_storage list to be deleted - :return: + :param idxs: list of indexes in self.exclusion_areas_storage list to be deleted + :type idxs: list + :return: None """ # delete all plotted shapes @@ -583,7 +625,8 @@ class ExclusionAreas(QtCore.QObject): def travel_coordinates(self, start_point, end_point, tooldia): """ - WIll create a path the go around the exclusion areas on the shortest path + WIll create a path the go around the exclusion areas on the shortest path when travelling (at a Z above the + material). :param start_point: X,Y coordinates for the start point of the travel line :type start_point: tuple diff --git a/assets/resources/dark_resources/etch_32.png b/assets/resources/dark_resources/etch_32.png index ec8687ab..2f06671b 100644 Binary files a/assets/resources/dark_resources/etch_32.png and b/assets/resources/dark_resources/etch_32.png differ diff --git a/assets/resources/etch_32.png b/assets/resources/etch_32.png index a0c23ce2..dd70dd74 100644 Binary files a/assets/resources/etch_32.png and b/assets/resources/etch_32.png differ diff --git a/camlib.py b/camlib.py index d8701b87..a718910a 100644 --- a/camlib.py +++ b/camlib.py @@ -964,6 +964,8 @@ class Geometry(object): corner_type = 1 if corner is None else corner geo_iso.append(pol.buffer(offset, int(self.geo_steps_per_circle), join_style=corner_type)) pol_nr += 1 + + # activity view update disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) if old_disp_number < disp_number <= 100: