diff --git a/CHANGELOG.md b/CHANGELOG.md index b83a0df5..a3e35ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM beta ================================================= +9.05.2020 + +- modified the GUI for Exclusion areas; now the shapes are displayed in a Table where they can be selected and deleted. Modification applied for Geometry Objects only (for now). +- fixed and error when converting units, error that acted when in those fields that accept lists of tools only one tool was added + 8.05.2020 - added a parameter to the FlatCAMDefaults class, whenever a value in the self.defaults dict change it will call a callback function and send to it the modified key diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 469b32d3..4641420d 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -4328,12 +4328,14 @@ class App(QtCore.QObject): ]: if self.defaults[dim] is None or self.defaults[dim] == '': continue + try: coordinates = self.defaults[dim].split(",") coords_xy = [float(eval(a)) for a in coordinates if a != ''] coords_xy[0] *= sfactor coords_xy[1] *= sfactor - self.defaults[dim] = "%.*f, %.*f" % (self.decimals, coords_xy[0], self.decimals, coords_xy[1]) + self.defaults[dim] = "%.*f, %.*f" % ( + self.decimals, coords_xy[0], self.decimals, coords_xy[1]) except Exception as e: log.debug("App.on_toggle_units.scale_defaults() --> 'string tuples': %s" % str(e)) @@ -4343,9 +4345,10 @@ class App(QtCore.QObject): if self.defaults[dim] is None or self.defaults[dim] == '': continue - if isinstance(self.defaults[dim], float): + try: + self.defaults[dim] = float(self.defaults[dim]) tools_diameters = [self.defaults[dim]] - else: + except ValueError: try: tools_string = self.defaults[dim].split(",") tools_diameters = [eval(a) for a in tools_string if a != ''] @@ -4354,9 +4357,14 @@ class App(QtCore.QObject): continue self.defaults[dim] = '' - for t in range(len(tools_diameters)): - tools_diameters[t] *= sfactor - self.defaults[dim] += "%.*f," % (self.decimals, tools_diameters[t]) + td_len = len(tools_diameters) + if td_len > 1: + for t in range(td_len): + tools_diameters[t] *= sfactor + self.defaults[dim] += "%.*f," % (self.decimals, tools_diameters[t]) + else: + tools_diameters[0] *= sfactor + self.defaults[dim] += "%.*f" % (self.decimals, tools_diameters[0]) elif dim in ['global_gridx', 'global_gridy']: # format the number of decimals to the one specified in self.decimals diff --git a/FlatCAMCommon.py b/FlatCAMCommon.py index d97bb6a4..b95091aa 100644 --- a/FlatCAMCommon.py +++ b/FlatCAMCommon.py @@ -10,6 +10,7 @@ # File Modified (major mod): Marius Adrian Stanciu # # Date: 11/4/2019 # # ########################################################## +from PyQt5 import QtCore from shapely.geometry import Polygon, MultiPolygon @@ -17,7 +18,6 @@ from flatcamGUI.VisPyVisuals import ShapeCollection from FlatCAMTool import FlatCAMTool import numpy as np -import re import gettext import FlatCAMTranslation as fcTranslate @@ -129,9 +129,13 @@ def color_variant(hex_color, bright_factor=1): return "#" + "".join([i for i in new_rgb]) -class ExclusionAreas: +class ExclusionAreas(QtCore.QObject): + + e_shape_modified = QtCore.pyqtSignal() def __init__(self, app): + super().__init__() + self.app = app # Storage for shapes, storage that can be used by FlatCAm tools for utility geometry @@ -401,6 +405,7 @@ class ExclusionAreas: '%s %s' % (_("Generate the CNC Job object."), _("With Exclusion areas.")) ) + self.e_shape_modified.emit() for k in self.exclusion_areas_storage: print(k) @@ -514,3 +519,52 @@ class ExclusionAreas: FlatCAMTool.delete_moving_selection_shape(self) self.app.delete_selection_shape() FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes) + self.app.inform.emit('[success] %s' % _("All exclusion zones deleted.")) + + def delete_sel_shapes(self, idxs): + """ + + :param idxs: list of indexes in self.exclusion_areas_storage list to be deleted + :return: + """ + + # delete all plotted shapes + FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes) + + # delete shapes + for idx in sorted(idxs, reverse=True): + del self.exclusion_areas_storage[idx] + + # re-add what's left after deletion in first step + if self.obj_type == 'excellon': + color = "#FF7400" + face_color = "#FF7400BF" + else: + color = "#098a8f" + face_color = "#FF7400BF" + + face_alpha = 0.3 + color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:] + + for geo_el in self.exclusion_areas_storage: + if isinstance(geo_el['shape'], Polygon): + self.exclusion_shapes.add( + geo_el['shape'], color=color, face_color=color_t, update=True, layer=0, tolerance=None) + if self.app.is_legacy is True: + self.exclusion_shapes.redraw() + + if self.exclusion_areas_storage: + self.app.inform.emit('[success] %s' % _("Selected exclusion zones deleted.")) + else: + # restore the default StyleSheet + self.cnc_button.setStyleSheet("") + # update the StyleSheet + self.cnc_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object.")) + + self.app.inform.emit('[success] %s' % _("All exclusion zones deleted.")) diff --git a/flatcamGUI/ObjectUI.py b/flatcamGUI/ObjectUI.py index 10b8f67d..8aa848d3 100644 --- a/flatcamGUI/ObjectUI.py +++ b/flatcamGUI/ObjectUI.py @@ -2125,35 +2125,29 @@ class GeometryObjectUI(ObjectUI): self.exclusion_box.setContentsMargins(0, 0, 0, 0) self.exclusion_frame.setLayout(self.exclusion_box) - h_lay = QtWidgets.QHBoxLayout() - self.exclusion_box.addLayout(h_lay) + self.exclusion_table = FCTable() + self.exclusion_box.addWidget(self.exclusion_table) + self.exclusion_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) - # Button Add Area - self.add_area_button = QtWidgets.QPushButton(_('Add area')) - self.add_area_button.setToolTip(_("Add an Exclusion Area.")) - h_lay.addWidget(self.add_area_button) + self.exclusion_table.setColumnCount(4) + self.exclusion_table.setColumnWidth(0, 20) + self.exclusion_table.setHorizontalHeaderLabels(['#', _('Object'), _('Strategy'), _('Over Z')]) - # Button Delete Area - self.delete_area_button = QtWidgets.QPushButton(_('Clear areas')) - self.delete_area_button.setToolTip(_("Delete all exclusion areas.")) - h_lay.addWidget(self.delete_area_button) + self.exclusion_table.horizontalHeaderItem(0).setToolTip(_("This is the Area ID.")) + self.exclusion_table.horizontalHeaderItem(1).setToolTip( + _("Type of the object where the exclusion area was added.")) + self.exclusion_table.horizontalHeaderItem(2).setToolTip( + _("The strategy used for exclusion area. Go around the exclusion areas or over it.")) + self.exclusion_table.horizontalHeaderItem(3).setToolTip( + _("If the strategy is to go over the area then this is the height at which the tool will go to avoid the " + "exclusion area.")) - grid_l = QtWidgets.QGridLayout() - grid_l.setColumnStretch(0, 0) - grid_l.setColumnStretch(1, 1) - self.exclusion_box.addLayout(grid_l) + self.exclusion_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - # Area Selection shape - self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape")) - self.area_shape_label.setToolTip( - _("The kind of selection shape used for area selection.") - ) - - self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'}, - {'label': _("Polygon"), 'value': 'polygon'}]) - - grid_l.addWidget(self.area_shape_label, 0, 0) - grid_l.addWidget(self.area_shape_radio, 0, 1) + grid_a1 = QtWidgets.QGridLayout() + grid_a1.setColumnStretch(0, 0) + grid_a1.setColumnStretch(1, 1) + self.exclusion_box.addLayout(grid_a1) # Chose Strategy self.strategy_label = FCLabel('%s:' % _("Strategy")) @@ -2164,8 +2158,8 @@ class GeometryObjectUI(ObjectUI): self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'}, {'label': _('Around'), 'value': 'around'}]) - grid_l.addWidget(self.strategy_label, 1, 0) - grid_l.addWidget(self.strategy_radio, 1, 1) + grid_a1.addWidget(self.strategy_label, 1, 0) + grid_a1.addWidget(self.strategy_radio, 1, 1) # Over Z self.over_z_label = FCLabel('%s:' % _("Over Z")) @@ -2175,8 +2169,36 @@ class GeometryObjectUI(ObjectUI): self.over_z_entry.set_range(0.000, 9999.9999) self.over_z_entry.set_precision(self.decimals) - grid_l.addWidget(self.over_z_label, 2, 0) - grid_l.addWidget(self.over_z_entry, 2, 1) + grid_a1.addWidget(self.over_z_label, 2, 0) + grid_a1.addWidget(self.over_z_entry, 2, 1) + + # Button Add Area + self.add_area_button = QtWidgets.QPushButton(_('Add area')) + self.add_area_button.setToolTip(_("Add an Exclusion Area.")) + + # Area Selection shape + self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'}, + {'label': _("Polygon"), 'value': 'polygon'}]) + self.area_shape_radio.setToolTip( + _("The kind of selection shape used for area selection.") + ) + + grid_a1.addWidget(self.add_area_button, 4, 0) + grid_a1.addWidget(self.area_shape_radio, 4, 1) + + h_lay_1 = QtWidgets.QHBoxLayout() + self.exclusion_box.addLayout(h_lay_1) + + # Button Delete All Areas + self.delete_area_button = QtWidgets.QPushButton(_('Clear areas')) + self.delete_area_button.setToolTip(_("Delete all exclusion areas.")) + + # Button Delete Selected Areas + self.delete_sel_area_button = QtWidgets.QPushButton(_('Delete Selected')) + self.delete_sel_area_button.setToolTip(_("Delete all exclusion areas that are selected in the table.")) + + h_lay_1.addWidget(self.delete_area_button) + h_lay_1.addWidget(self.delete_sel_area_button) # -------------------------- EXCLUSION AREAS END ------------------------------------------------------------- # ------------------------------------------------------------------------------------------------------------ diff --git a/flatcamObjects/FlatCAMGeometry.py b/flatcamObjects/FlatCAMGeometry.py index 9cfeab20..18d9aaae 100644 --- a/flatcamObjects/FlatCAMGeometry.py +++ b/flatcamObjects/FlatCAMGeometry.py @@ -159,6 +159,15 @@ class GeometryObject(FlatCAMObj, Geometry): self.ui_disconnect() FlatCAMObj.build_ui(self) + # Area Exception - exclusion shape added signal + # first disconnect it from any other object + try: + self.app.exc_areas.e_shape_modified.disconnect() + except (TypeError, AttributeError): + pass + # then connect it to the current build_ui() method + self.app.exc_areas.e_shape_modified.connect(self.build_ui) + self.units = self.app.defaults['units'] tool_idx = 0 @@ -179,7 +188,6 @@ class GeometryObject(FlatCAMObj, Geometry): # For INCH the decimals should be no more than 3. There are no tools under 10mils. dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooluid_value['tooldia']))) - dia_item.setFlags(QtCore.Qt.ItemIsEnabled) offset_item = FCComboBox() @@ -310,6 +318,58 @@ class GeometryObject(FlatCAMObj, Geometry): "%s: %s" % (_('Parameters for'), _("Multiple Tools")) ) + # Build Exclusion Areas section + e_len = len(self.app.exc_areas.exclusion_areas_storage) + self.ui.exclusion_table.setRowCount(e_len) + + area_id = 0 + + for area in range(e_len): + area_id += 1 + + area_dict = self.app.exc_areas.exclusion_areas_storage[area] + + area_id_item = QtWidgets.QTableWidgetItem('%d' % int(area_id)) + area_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.ui.exclusion_table.setItem(area, 0, area_id_item) # Area id + + object_item = QtWidgets.QTableWidgetItem('%s' % area_dict["obj_type"]) + object_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.ui.exclusion_table.setItem(area, 1, object_item) # Origin Object + + strategy_item = QtWidgets.QTableWidgetItem('%s' % area_dict["strategy"]) + strategy_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.ui.exclusion_table.setItem(area, 2, strategy_item) # Strategy + + overz_item = QtWidgets.QTableWidgetItem('%s' % area_dict["overz"]) + overz_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.ui.exclusion_table.setItem(area, 3, overz_item) # Over Z + + self.ui.exclusion_table.resizeColumnsToContents() + self.ui.exclusion_table.resizeRowsToContents() + + area_vheader = self.ui.exclusion_table.verticalHeader() + area_vheader.hide() + self.ui.exclusion_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + area_hheader = self.ui.exclusion_table.horizontalHeader() + area_hheader.setMinimumSectionSize(10) + area_hheader.setDefaultSectionSize(70) + + area_hheader.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + area_hheader.resizeSection(0, 20) + area_hheader.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) + area_hheader.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) + area_hheader.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + + # area_hheader.setStretchLastSection(True) + self.ui.exclusion_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + self.ui.exclusion_table.setColumnWidth(0, 20) + + self.ui.exclusion_table.setMinimumHeight(self.ui.exclusion_table.getHeight()) + self.ui.exclusion_table.setMaximumHeight(self.ui.exclusion_table.getHeight()) + def set_ui(self, ui): FlatCAMObj.set_ui(self, ui) @@ -534,8 +594,11 @@ class GeometryObject(FlatCAMObj, Geometry): self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked) self.ui.cutz_entry.returnPressed.connect(self.on_cut_z_changed) + # Exclusion areas + self.ui.exclusion_table.horizontalHeader().sectionClicked.connect(self.ui.exclusion_table.selectAll) self.ui.add_area_button.clicked.connect(self.on_add_area_click) self.ui.delete_area_button.clicked.connect(self.on_clear_area_click) + self.ui.delete_sel_area_button.clicked.connect(self.on_delete_sel_areas) def on_cut_z_changed(self): self.old_cutz = self.ui.cutz_entry.get_value() @@ -2463,6 +2526,20 @@ class GeometryObject(FlatCAMObj, Geometry): def on_clear_area_click(self): self.app.exc_areas.on_clear_area_click() + self.app.exc_areas.e_shape_modified.emit() + + def on_delete_sel_areas(self): + sel_model = self.ui.exclusion_table.selectionModel() + sel_indexes = sel_model.selectedIndexes() + + # it will iterate over all indexes which means all items in all columns too but I'm interested only on rows + # so the duplicate rows will not be added + sel_rows = set() + for idx in sel_indexes: + sel_rows.add(idx.row()) + + self.app.exc_areas.delete_sel_shapes(idxs=list(sel_rows)) + self.app.exc_areas.e_shape_modified.emit() def plot_element(self, element, color=None, visible=None): diff --git a/flatcamObjects/FlatCAMObj.py b/flatcamObjects/FlatCAMObj.py index 94108cc9..19ee7257 100644 --- a/flatcamObjects/FlatCAMObj.py +++ b/flatcamObjects/FlatCAMObj.py @@ -308,8 +308,8 @@ class FlatCAMObj(QtCore.QObject): for option in self.options: try: self.set_form_item(option) - except Exception: - self.app.log.warning("Unexpected error:", sys.exc_info()) + except Exception as err: + self.app.log.warning("Unexpected error: %s" % str(sys.exc_info()), str(err)) def read_form(self): """ @@ -323,7 +323,7 @@ class FlatCAMObj(QtCore.QObject): try: self.read_form_item(option) except Exception: - self.app.log.warning("Unexpected error:", sys.exc_info()) + self.app.log.warning("Unexpected error: %s" % str(sys.exc_info())) def set_form_item(self, option): """