From 4ece23c456d889a635c279e6e1570ee8977946bb Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 7 May 2020 00:46:02 +0300 Subject: [PATCH 1/5] - added a fix so the app close is now clean, with exit code 0 as set --- CHANGELOG.md | 4 ++++ FlatCAMApp.py | 14 +++++++++----- FlatCAMCommon.py | 24 ------------------------ FlatCAMTranslation.py | 3 +-- flatcamObjects/FlatCAMGeometry.py | 18 +++++++----------- 5 files changed, 21 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbcc3f4c..75b8e67c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM beta ================================================= +7.05.2020 + +- added a fix so the app close is now clean, with exit code 0 as set + 6.05.2020 - wip in adding Exclusion areas in Geometry object; each Geometry object has now a storage for shapes (exclusion shapes, should I make them more general?) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index eb4e9569..d845b0c2 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -3598,14 +3598,13 @@ class App(QtCore.QObject): # try to quit the Socket opened by ArgsThread class try: - self.new_launch.thread_exit = True - self.new_launch.listener.close() + self.new_launch.stop.emit() except Exception as err: log.debug("App.quit_application() --> %s" % str(err)) # try to quit the QThread that run ArgsThread class try: - self.th.terminate() + self.th.quit() except Exception as e: log.debug("App.quit_application() --> %s" % str(e)) @@ -3615,7 +3614,6 @@ class App(QtCore.QObject): # quit app by signalling for self.kill_app() method # self.close_app_signal.emit() QtWidgets.qApp.quit() - # QtCore.QCoreApplication.exit() # When the main event loop is not started yet in which case the qApp.quit() will do nothing # we use the following command @@ -3627,7 +3625,6 @@ class App(QtCore.QObject): @staticmethod def kill_app(): - # QtCore.QCoreApplication.quit() QtWidgets.qApp.quit() # When the main event loop is not started yet in which case the qApp.quit() will do nothing # we use the following command @@ -10888,6 +10885,7 @@ class App(QtCore.QObject): class ArgsThread(QtCore.QObject): open_signal = pyqtSignal(list) start = pyqtSignal() + stop = pyqtSignal() if sys.platform == 'win32': address = (r'\\.\pipe\NPtest', 'AF_PIPE') @@ -10900,6 +10898,7 @@ class ArgsThread(QtCore.QObject): self.thread_exit = False self.start.connect(self.run) + self.stop.connect(self.close_listener) def my_loop(self, address): try: @@ -10943,4 +10942,9 @@ class ArgsThread(QtCore.QObject): def run(self): self.my_loop(self.address) + @pyqtSlot() + def close_listener(self): + self.thread_exit = True + self.listener.close() + # end of file diff --git a/FlatCAMCommon.py b/FlatCAMCommon.py index c29ac4f3..ab60dccd 100644 --- a/FlatCAMCommon.py +++ b/FlatCAMCommon.py @@ -491,27 +491,3 @@ class ExclusionAreas: FlatCAMTool.delete_moving_selection_shape(self) self.app.delete_selection_shape() FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes) - - -class InvertHexColor: - """ - Will invert a hex color made out of 3 chars or 6 chars - From here: http://code.activestate.com/recipes/527747-invert-css-hex-colors/ - """ - def __init__(self): - self.p6 = re.compile("#[0-9a-f]{6};", re.IGNORECASE) - self.p3 = re.compile("#[0-9a-f]{3};", re.IGNORECASE) - - def modify(self, original_color=3): - code = {} - l1 = "#;0123456789abcdef" - l2 = "#;fedcba9876543210" - - for i in range(len(l1)): - code[l1[i]] = l2[i] - inverted = "" - - content = p6.sub(modify, content) - content = p3.sub(modify, content) - return inverted - diff --git a/FlatCAMTranslation.py b/FlatCAMTranslation.py index 4e8edbf3..82a8ac33 100644 --- a/FlatCAMTranslation.py +++ b/FlatCAMTranslation.py @@ -186,8 +186,7 @@ def restart_program(app, ask=None): # try to quit the Socket opened by ArgsThread class try: - app.new_launch.thread_exit = True - app.new_launch.listener.close() + app.new_launch.stop.emit() except Exception as err: log.debug("FlatCAMTranslation.restart_program() --> %s" % str(err)) diff --git a/flatcamObjects/FlatCAMGeometry.py b/flatcamObjects/FlatCAMGeometry.py index 1d2bad73..9cfeab20 100644 --- a/flatcamObjects/FlatCAMGeometry.py +++ b/flatcamObjects/FlatCAMGeometry.py @@ -1674,15 +1674,11 @@ class GeometryObject(FlatCAMObj, Geometry): The actual work is done by the target CNCJobObject object's `generate_from_geometry_2()` method. - :param tools_dict: a dictionary that holds the whole data needed to create the Gcode - (including the solid_geometry) - - :param tools_in_use: the tools that are used, needed by some preprocessors - :type list of lists, each list in the list is made out of row elements of tools table from GUI - :param outname: - :param tools_dict: - :param tools_in_use: + :param tools_dict: a dictionary that holds the whole data needed to create the Gcode + (including the solid_geometry) + :param tools_in_use: the tools that are used, needed by some preprocessors + :type tools_in_use list of lists, each list in the list is made out of row elements of tools table from GUI :param segx: number of segments on the X axis, for auto-levelling :param segy: number of segments on the Y axis, for auto-levelling :param plot: if True the generated object will be plotted; if False will not be plotted @@ -1726,7 +1722,7 @@ class GeometryObject(FlatCAMObj, Geometry): # count the tools tool_cnt = 0 - dia_cnc_dict = {} + # dia_cnc_dict = {} # this turn on the FlatCAMCNCJob plot for multiple tools job_obj.multitool = True @@ -1866,7 +1862,7 @@ class GeometryObject(FlatCAMObj, Geometry): # count the tools tool_cnt = 0 - dia_cnc_dict = {} + # dia_cnc_dict = {} # this turn on the FlatCAMCNCJob plot for multiple tools job_obj.multitool = True @@ -2040,7 +2036,7 @@ class GeometryObject(FlatCAMObj, Geometry): use_thread=True, plot=True): """ - Only used for TCL Command. + Only used by the TCL Command Cncjob. Creates a CNCJob out of this Geometry object. The actual work is done by the target camlib.CNCjob `generate_from_geometry_2()` method. From eea80aafc32d101559c4da55050b05306e4e0c4d Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 7 May 2020 01:38:08 +0300 Subject: [PATCH 2/5] - added the ability to add exclusion areas from the Excellon object too. Now there is a different in color to differentiate from which type of object the exclusion areas were added but they all serve the same purpose --- CHANGELOG.md | 1 + FlatCAMCommon.py | 39 +++++++++++--- defaults.py | 4 ++ flatcamGUI/ObjectUI.py | 85 +++++++++++++++++++++++++++++-- flatcamObjects/FlatCAMExcellon.py | 24 ++++++++- 5 files changed, 141 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75b8e67c..1b5e26b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG for FlatCAM beta 7.05.2020 - added a fix so the app close is now clean, with exit code 0 as set +- added the ability to add exclusion areas from the Excellon object too. Now there is a different in color to differentiate from which type of object the exclusion areas were added but they all serve the same purpose 6.05.2020 diff --git a/FlatCAMCommon.py b/FlatCAMCommon.py index ab60dccd..476eb1cc 100644 --- a/FlatCAMCommon.py +++ b/FlatCAMCommon.py @@ -275,11 +275,18 @@ class ExclusionAreas: } self.exclusion_areas_storage.append(new_el) + if self.obj_type == 'excellon': + color = "#FF7400" + face_color = "#FF7400BF" + else: + color = "#098a8f" + face_color = "#098a8fBF" + # add a temporary shape on canvas FlatCAMTool.draw_tool_selection_shape( self, old_coords=(x0, y0), coords=(x1, y1), - color="#FF7400", - face_color="#FF7400BF", + color=color, + face_color=face_color, shapes_storage=self.exclusion_shapes) self.first_click = False @@ -328,10 +335,18 @@ class ExclusionAreas: "overz": self.over_z } self.exclusion_areas_storage.append(new_el) + + if self.obj_type == 'excellon': + color = "#FF7400" + face_color = "#FF7400BF" + else: + color = "#098a8f" + face_color = "#098a8fBF" + FlatCAMTool.draw_selection_shape_polygon( self, points=self.points, - color="#FF7400", - face_color="#FF7400BF", + color=color, + face_color=face_color, shapes_storage=self.exclusion_shapes) self.app.inform.emit( _("Zone added. Click to start adding next zone or right click to finish.")) @@ -456,20 +471,28 @@ class ExclusionAreas: self.app.ui.rel_position_label.setText("Dx: %.4f   Dy: " "%.4f    " % (self.app.dx, self.app.dy)) + if self.obj_type == 'excellon': + color = "#FF7400" + face_color = "#FF7400BF" + else: + color = "#098a8f" + face_color = "#098a8fBF" + # draw the utility geometry if shape_type == "square": if self.first_click: self.app.delete_selection_shape() + self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]), - color="#FF7400", - face_color="#FF7400BF", + color=color, + face_color=face_color, coords=(curr_pos[0], curr_pos[1])) else: FlatCAMTool.delete_moving_selection_shape(self) FlatCAMTool.draw_moving_selection_shape_poly( self, points=self.points, - color="#FF7400", - face_color="#FF7400BF", + color=color, + face_color=face_color, data=(curr_pos[0], curr_pos[1])) def on_clear_area_click(self): diff --git a/defaults.py b/defaults.py index ffd84874..a730dfe9 100644 --- a/defaults.py +++ b/defaults.py @@ -263,6 +263,10 @@ class FlatCAMDefaults: "excellon_tooldia": 0.8, "excellon_slot_tooldia": 1.8, "excellon_gcode_type": "drills", + "excellon_area_exclusion": False, + "excellon_area_shape": "polygon", + "excellon_area_strategy": "over", + "excellon_area_overz": 1.0, # Excellon Advanced Options "excellon_offset": 0.0, diff --git a/flatcamGUI/ObjectUI.py b/flatcamGUI/ObjectUI.py index c5dacb42..f683ea18 100644 --- a/flatcamGUI/ObjectUI.py +++ b/flatcamGUI/ObjectUI.py @@ -1292,10 +1292,89 @@ class ExcellonObjectUI(ObjectUI): self.grid5.addWidget(pp_geo_label, 16, 0) self.grid5.addWidget(self.pp_geo_name_cb, 16, 1) + # Exclusion Areas + self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas")) + self.exclusion_cb.setToolTip( + _( + "Include exclusion areas.\n" + "In those areas the travel of the tools\n" + "is forbidden." + ) + ) + self.grid5.addWidget(self.exclusion_cb, 17, 0, 1, 2) + + # ------------------------------------------------------------------------------------------------------------ + # ------------------------- EXCLUSION AREAS ------------------------------------------------------------------ + # ------------------------------------------------------------------------------------------------------------ + self.exclusion_frame = QtWidgets.QFrame() + self.exclusion_frame.setContentsMargins(0, 0, 0, 0) + self.grid5.addWidget(self.exclusion_frame, 18, 0, 1, 2) + + self.exclusion_box = QtWidgets.QVBoxLayout() + 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) + + # 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) + + # 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) + + grid_l = QtWidgets.QGridLayout() + grid_l.setColumnStretch(0, 0) + grid_l.setColumnStretch(1, 1) + self.exclusion_box.addLayout(grid_l) + + # 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) + + # Chose Strategy + self.strategy_label = FCLabel('%s:' % _("Strategy")) + self.strategy_label.setToolTip(_("The strategy followed when encountering an exclusion area.\n" + "Can be:\n" + "- Over -> when encountering the area, the tool will go to a set height\n" + "- Around -> will avoid the exclusion area by going around the area")) + 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) + + # Over Z + self.over_z_label = FCLabel('%s:' % _("Over Z")) + self.over_z_label.setToolTip(_("The height Z to which the tool will rise in order to avoid\n" + "an interdiction area.")) + self.over_z_entry = FCDoubleSpinner() + 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) + + # -------------------------- EXCLUSION AREAS END ------------------------------------------------------------- + # ------------------------------------------------------------------------------------------------------------ + self.ois_exclusion_geo = OptionalHideInputSection(self.exclusion_cb, [self.exclusion_frame]) + separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.HLine) separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - self.grid5.addWidget(separator_line, 17, 0, 1, 2) + self.grid5.addWidget(separator_line, 19, 0, 1, 2) # ################################################################# # ################# GRID LAYOUT 6 ############################### @@ -2025,7 +2104,7 @@ class GeometryObjectUI(ObjectUI): # grid4.addWidget(QtWidgets.QLabel(''), 12, 0, 1, 2) # Exclusion Areas - self.exclusion_cb = FCCheckBox('%s:' % _("Exclusion areas")) + self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas")) self.exclusion_cb.setToolTip( _( "Include exclusion areas.\n" @@ -2101,7 +2180,7 @@ class GeometryObjectUI(ObjectUI): # -------------------------- EXCLUSION AREAS END ------------------------------------------------------------- # ------------------------------------------------------------------------------------------------------------ - self.ois_exclusion_geo = OptionalInputSection(self.exclusion_cb, [self.exclusion_frame]) + self.ois_exclusion_geo = OptionalHideInputSection(self.exclusion_cb, [self.exclusion_frame]) warning_lbl = QtWidgets.QLabel( _( diff --git a/flatcamObjects/FlatCAMExcellon.py b/flatcamObjects/FlatCAMExcellon.py index 691fb897..a682495b 100644 --- a/flatcamObjects/FlatCAMExcellon.py +++ b/flatcamObjects/FlatCAMExcellon.py @@ -567,7 +567,11 @@ class ExcellonObject(FlatCAMObj, Excellon): "ppname_g": self.ui.pp_geo_name_cb, "z_pdepth": self.ui.pdepth_entry, "feedrate_probe": self.ui.feedrate_probe_entry, - # "gcode_type": self.ui.excellon_gcode_type_radio + # "gcode_type": self.ui.excellon_gcode_type_radio, + "area_exclusion": self.ui.exclusion_cb, + "area_shape": self.ui.area_shape_radio, + "area_strategy": self.ui.strategy_radio, + "area_overz": self.ui.over_z_entry, }) self.name2option = { @@ -634,6 +638,9 @@ class ExcellonObject(FlatCAMObj, Excellon): self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click) self.ui.generate_milling_slots_button.clicked.connect(self.on_generate_milling_slots_button_click) + 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.on_operation_type(val='drill') self.ui.operation_radio.activated_custom.connect(self.on_operation_type) @@ -1467,6 +1474,21 @@ class ExcellonObject(FlatCAMObj, Excellon): # self.options['startz'] = float(self.options['startz']) * factor # self.options['endz'] = float(self.options['endz']) * factor + def on_add_area_click(self): + shape_button = self.ui.area_shape_radio + overz_button = self.ui.over_z_entry + strategy_radio = self.ui.strategy_radio + cnc_button = self.ui.generate_cnc_button + solid_geo = self.solid_geometry + obj_type = self.kind + + self.app.exc_areas.on_add_area_click( + shape_button=shape_button, overz_button=overz_button, cnc_button=cnc_button, strategy_radio=strategy_radio, + solid_geo=solid_geo, obj_type=obj_type) + + def on_clear_area_click(self): + self.app.exc_areas.on_clear_area_click() + def on_solid_cb_click(self, *args): if self.muted_ui: return From 484fb51bf0359ed64e4ade77e7bd9177f57b8d97 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 8 May 2020 01:41:40 +0300 Subject: [PATCH 3/5] - 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 - optimized and fixed some issues in the self.on_toggle_units() method - the Exclusion areas will have all the orange color but the color of the outline will differ according to the type of the object from where it was added (cosmetic use only as the Exclusion areas will be applied globally) --- CHANGELOG.md | 8 +- FlatCAMApp.py | 162 ++++-------------- FlatCAMCommon.py | 6 +- defaults.py | 10 +- flatcamEditors/FlatCAMGeoEditor.py | 4 +- flatcamEditors/FlatCAMGrbEditor.py | 4 +- flatcamGUI/ObjectUI.py | 4 +- flatcamGUI/PlotCanvas.py | 1 + .../tools/ToolsTransformPrefGroupUI.py | 4 +- flatcamTools/ToolTransform.py | 6 +- 10 files changed, 64 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b5e26b3..16b5fd6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,16 @@ CHANGELOG for FlatCAM beta ================================================= +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 +- optimized and fixed some issues in the self.on_toggle_units() method +- the Exclusion areas will have all the orange color but the color of the outline will differ according to the type of the object from where it was added (cosmetic use only as the Exclusion areas will be applied globally) + 7.05.2020 - added a fix so the app close is now clean, with exit code 0 as set -- added the ability to add exclusion areas from the Excellon object too. Now there is a different in color to differentiate from which type of object the exclusion areas were added but they all serve the same purpose +- added the ability to add exclusion areas from the Excellon object too. Now there is a difference in color to differentiate from which type of object the exclusion areas were added but they all serve the same purpose 6.05.2020 diff --git a/FlatCAMApp.py b/FlatCAMApp.py index d845b0c2..469b32d3 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -4244,10 +4244,10 @@ class App(QtCore.QObject): # If option is the same, then ignore if new_units == self.defaults["units"].upper(): - self.log.debug("on_toggle_units(): Same as defaults, so ignoring.") + self.log.debug("on_toggle_units(): Same as previous, ignoring.") return - # Options to scale + # Keys in self.defaults for which to scale their values dimensions = ['gerber_isotooldia', 'gerber_noncoppermargin', 'gerber_bboxmargin', "gerber_editor_newsize", "gerber_editor_lin_pitch", "gerber_editor_buff_f", "gerber_vtipdia", "gerber_vcutz", "gerber_editor_newdim", "gerber_editor_ma_low", @@ -4321,149 +4321,54 @@ class App(QtCore.QObject): def scale_defaults(sfactor): for dim in dimensions: + if dim in [ + 'gerber_editor_newdim', 'excellon_toolchangexy', 'geometry_toolchangexy', 'excellon_endxy', + 'geometry_endxy', 'tools_solderpaste_xy_toolchange', 'tools_cal_toolchange_xy', + 'tools_transform_mirror_point' + ]: + 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]) + except Exception as e: + log.debug("App.on_toggle_units.scale_defaults() --> 'string tuples': %s" % str(e)) - if dim == 'gerber_editor_newdim': - if self.defaults["gerber_editor_newdim"] is None or self.defaults["gerber_editor_newdim"] == '': - continue - coordinates = self.defaults["gerber_editor_newdim"].split(",") - coords_xy = [float(eval(a)) for a in coordinates if a != ''] - coords_xy[0] *= sfactor - coords_xy[1] *= sfactor - self.defaults['gerber_editor_newdim'] = "%.*f, %.*f" % (self.decimals, coords_xy[0], - self.decimals, coords_xy[1]) - if dim == 'excellon_toolchangexy': - if self.defaults["excellon_toolchangexy"] is None or self.defaults["excellon_toolchangexy"] == '': - continue - coordinates = self.defaults["excellon_toolchangexy"].split(",") - coords_xy = [float(eval(a)) for a in coordinates if a != ''] - coords_xy[0] *= sfactor - coords_xy[1] *= sfactor - self.defaults['excellon_toolchangexy'] = "%.*f, %.*f" % (self.decimals, coords_xy[0], - self.decimals, coords_xy[1]) - elif dim == 'geometry_toolchangexy': - if self.defaults["geometry_toolchangexy"] is None or self.defaults["geometry_toolchangexy"] == '': - continue - coordinates = self.defaults["geometry_toolchangexy"].split(",") - coords_xy = [float(eval(a)) for a in coordinates if a != ''] - coords_xy[0] *= sfactor - coords_xy[1] *= sfactor - self.defaults['geometry_toolchangexy'] = "%.*f, %.*f" % (self.decimals, coords_xy[0], - self.decimals, coords_xy[1]) - elif dim == 'excellon_endxy': - if self.defaults["excellon_endxy"] is None or self.defaults["excellon_endxy"] == '': + elif dim in [ + 'geometry_cnctooldia', 'tools_ncctools', 'tools_solderpaste_tools' + ]: + if self.defaults[dim] is None or self.defaults[dim] == '': continue - coordinates = self.defaults["excellon_endxy"].split(",") - end_coords_xy = [float(eval(a)) for a in coordinates if a != ''] - end_coords_xy[0] *= sfactor - end_coords_xy[1] *= sfactor - self.defaults['excellon_endxy'] = "%.*f, %.*f" % (self.decimals, end_coords_xy[0], - self.decimals, end_coords_xy[1]) - elif dim == 'geometry_endxy': - if self.defaults["geometry_endxy"] is None or self.defaults["geometry_endxy"] == '': - continue - coordinates = self.defaults["geometry_endxy"].split(",") - end_coords_xy = [float(eval(a)) for a in coordinates if a != ''] - end_coords_xy[0] *= sfactor - end_coords_xy[1] *= sfactor - self.defaults['geometry_endxy'] = "%.*f, %.*f" % (self.decimals, end_coords_xy[0], - self.decimals, end_coords_xy[1]) - - elif dim == 'geometry_cnctooldia': - if self.defaults["geometry_cnctooldia"] is None or self.defaults["geometry_cnctooldia"] == '': - continue - if type(self.defaults["geometry_cnctooldia"]) is float: - tools_diameters = [self.defaults["geometry_cnctooldia"]] + if isinstance(self.defaults[dim], float): + tools_diameters = [self.defaults[dim]] else: try: - tools_string = self.defaults["geometry_cnctooldia"].split(",") + tools_string = self.defaults[dim].split(",") tools_diameters = [eval(a) for a in tools_string if a != ''] except Exception as e: log.debug("App.on_toggle_units().scale_options() --> %s" % str(e)) continue - self.defaults['geometry_cnctooldia'] = '' + self.defaults[dim] = '' for t in range(len(tools_diameters)): tools_diameters[t] *= sfactor - self.defaults['geometry_cnctooldia'] += "%.*f," % (self.decimals, tools_diameters[t]) - elif dim == 'tools_ncctools': - if self.defaults["tools_ncctools"] is None or self.defaults["tools_ncctools"] == '': - continue - if type(self.defaults["tools_ncctools"]) == float: - ncctools = [self.defaults["tools_ncctools"]] - else: - try: - tools_string = self.defaults["tools_ncctools"].split(",") - ncctools = [eval(a) for a in tools_string if a != ''] - except Exception as e: - log.debug("App.on_toggle_units().scale_options() --> %s" % str(e)) - continue + self.defaults[dim] += "%.*f," % (self.decimals, tools_diameters[t]) - self.defaults['tools_ncctools'] = '' - for t in range(len(ncctools)): - ncctools[t] *= sfactor - self.defaults['tools_ncctools'] += "%.*f," % (self.decimals, ncctools[t]) - elif dim == 'tools_solderpaste_tools': - if self.defaults["tools_solderpaste_tools"] is None or \ - self.defaults["tools_solderpaste_tools"] == '': - continue - if type(self.defaults["tools_solderpaste_tools"]) == float: - sptools = [self.defaults["tools_solderpaste_tools"]] - else: - try: - tools_string = self.defaults["tools_solderpaste_tools"].split(",") - sptools = [eval(a) for a in tools_string if a != ''] - except Exception as e: - log.debug("App.on_toggle_units().scale_options() --> %s" % str(e)) - continue - - self.defaults['tools_solderpaste_tools'] = "" - for t in range(len(sptools)): - sptools[t] *= sfactor - self.defaults['tools_solderpaste_tools'] += "%.*f," % (self.decimals, sptools[t]) - elif dim == 'tools_solderpaste_xy_toolchange': - if self.defaults["tools_solderpaste_xy_toolchange"] is None or \ - self.defaults["tools_solderpaste_xy_toolchange"] == '': - continue + elif dim in ['global_gridx', 'global_gridy']: + # format the number of decimals to the one specified in self.decimals try: - coordinates = self.defaults["tools_solderpaste_xy_toolchange"].split(",") - sp_coords = [float(eval(a)) for a in coordinates if a != ''] - sp_coords[0] *= sfactor - sp_coords[1] *= sfactor - self.defaults['tools_solderpaste_xy_toolchange'] = "%.*f, %.*f" % (self.decimals, sp_coords[0], - self.decimals, sp_coords[1]) + val = float(self.defaults[dim]) * sfactor except Exception as e: - log.debug("App.on_toggle_units().scale_options() --> %s" % str(e)) + log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e)) continue - elif dim == 'tools_cal_toolchange_xy': - if self.defaults["tools_cal_toolchange_xy"] is None or \ - self.defaults["tools_cal_toolchange_xy"] == '': - continue - coordinates = self.defaults["tools_cal_toolchange_xy"].split(",") - end_coords_xy = [float(eval(a)) for a in coordinates if a != ''] - end_coords_xy[0] *= sfactor - end_coords_xy[1] *= sfactor - self.defaults['tools_cal_toolchange_xy'] = "%.*f, %.*f" % (self.decimals, end_coords_xy[0], - self.decimals, end_coords_xy[1]) - elif dim == 'global_gridx' or dim == 'global_gridy': - if new_units == 'IN': - try: - val = float(self.defaults[dim]) * sfactor - except Exception as e: - log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e)) - continue - - self.defaults[dim] = float('%.*f' % (self.decimals, val)) - else: - try: - val = float(self.defaults[dim]) * sfactor - except Exception as e: - log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e)) - continue - - self.defaults[dim] = float('%.*f' % (self.decimals, val)) + self.defaults[dim] = float('%.*f' % (self.decimals, val)) else: + # the number of decimals for the rest is kept unchanged if self.defaults[dim]: try: val = float(self.defaults[dim]) * sfactor @@ -4526,10 +4431,11 @@ class App(QtCore.QObject): # replot all objects self.plot_all() + # set the status labels to reflect the current FlatCAM units self.set_screen_units(new_units) - # signal to the app that we changed the object properties and it shoud save the project + # signal to the app that we changed the object properties and it should save the project self.should_we_save = True self.inform.emit('[success] %s: %s' % (_("Converted units to"), new_units)) diff --git a/FlatCAMCommon.py b/FlatCAMCommon.py index 476eb1cc..d97bb6a4 100644 --- a/FlatCAMCommon.py +++ b/FlatCAMCommon.py @@ -280,7 +280,7 @@ class ExclusionAreas: face_color = "#FF7400BF" else: color = "#098a8f" - face_color = "#098a8fBF" + face_color = "#FF7400BF" # add a temporary shape on canvas FlatCAMTool.draw_tool_selection_shape( @@ -341,7 +341,7 @@ class ExclusionAreas: face_color = "#FF7400BF" else: color = "#098a8f" - face_color = "#098a8fBF" + face_color = "#FF7400BF" FlatCAMTool.draw_selection_shape_polygon( self, points=self.points, @@ -476,7 +476,7 @@ class ExclusionAreas: face_color = "#FF7400BF" else: color = "#098a8f" - face_color = "#098a8fBF" + face_color = "#FF7400BF" # draw the utility geometry if shape_type == "square": diff --git a/defaults.py b/defaults.py index a730dfe9..b446a39a 100644 --- a/defaults.py +++ b/defaults.py @@ -495,7 +495,7 @@ class FlatCAMDefaults: "tools_transform_offset_x": 0.0, "tools_transform_offset_y": 0.0, "tools_transform_mirror_reference": False, - "tools_transform_mirror_point": (0, 0), + "tools_transform_mirror_point": "0.0, 0.0", "tools_transform_buffer_dis": 0.0, "tools_transform_buffer_factor": 100.0, "tools_transform_buffer_corner": True, @@ -697,13 +697,19 @@ class FlatCAMDefaults: except Exception as e: log.error("save_factory_defaults() -> %s" % str(e)) - def __init__(self): + def __init__(self, callback=lambda x: None): + """ + + :param callback: A method called each time that one of the values are changed in the self.defaults LouDict + """ self.defaults = LoudDict() self.defaults.update(self.factory_defaults) self.current_defaults = {} # copy used for restoring after cancelled prefs changes self.current_defaults.update(self.factory_defaults) self.old_defaults_found = False + self.defaults.set_change_callback(callback) + # #### Pass-through to the defaults LoudDict ##### def __len__(self): return self.defaults.__len__() diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py index 1f10c01f..ccf67ce4 100644 --- a/flatcamEditors/FlatCAMGeoEditor.py +++ b/flatcamEditors/FlatCAMGeoEditor.py @@ -933,7 +933,7 @@ class TransformEditorTool(FlatCAMTool): "the 'y' in (x, y) will be used when using Flip on Y.") ) self.flip_ref_label.setFixedWidth(50) - self.flip_ref_entry = FCEntry("(0, 0)") + self.flip_ref_entry = FCEntry("0, 0") self.flip_ref_button = FCButton() self.flip_ref_button.set_value(_("Add")) @@ -1048,7 +1048,7 @@ class TransformEditorTool(FlatCAMTool): if self.app.defaults["tools_transform_mirror_point"]: self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"]) else: - self.flip_ref_entry.set_value((0, 0)) + self.flip_ref_entry.set_value("0, 0") def template(self): if not self.draw_app.selected: diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index 9aa85c78..3362c5ba 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -5633,7 +5633,7 @@ class TransformEditorTool(FlatCAMTool): "the 'y' in (x, y) will be used when using Flip on Y.") ) self.flip_ref_label.setMinimumWidth(50) - self.flip_ref_entry = EvalEntry2("(0, 0)") + self.flip_ref_entry = FCEntry() self.flip_ref_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) # self.flip_ref_entry.setFixedWidth(60) @@ -5760,7 +5760,7 @@ class TransformEditorTool(FlatCAMTool): if self.app.defaults["tools_transform_mirror_point"]: self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"]) else: - self.flip_ref_entry.set_value((0, 0)) + self.flip_ref_entry.set_value("0, 0") def template(self): if not self.draw_app.selected: diff --git a/flatcamGUI/ObjectUI.py b/flatcamGUI/ObjectUI.py index f683ea18..10b8f67d 100644 --- a/flatcamGUI/ObjectUI.py +++ b/flatcamGUI/ObjectUI.py @@ -1293,7 +1293,7 @@ class ExcellonObjectUI(ObjectUI): self.grid5.addWidget(self.pp_geo_name_cb, 16, 1) # Exclusion Areas - self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas")) + self.exclusion_cb = FCCheckBox('%s' % _("Add exclusion areas")) self.exclusion_cb.setToolTip( _( "Include exclusion areas.\n" @@ -2104,7 +2104,7 @@ class GeometryObjectUI(ObjectUI): # grid4.addWidget(QtWidgets.QLabel(''), 12, 0, 1, 2) # Exclusion Areas - self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas")) + self.exclusion_cb = FCCheckBox('%s' % _("Add exclusion areas")) self.exclusion_cb.setToolTip( _( "Include exclusion areas.\n" diff --git a/flatcamGUI/PlotCanvas.py b/flatcamGUI/PlotCanvas.py index de7a7028..44af74d0 100644 --- a/flatcamGUI/PlotCanvas.py +++ b/flatcamGUI/PlotCanvas.py @@ -287,6 +287,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas): def on_mouse_scroll(self, event): # key modifiers modifiers = event.modifiers + pan_delta_x = self.fcapp.defaults["global_gridx"] pan_delta_y = self.fcapp.defaults["global_gridy"] curr_pos = event.pos diff --git a/flatcamGUI/preferences/tools/ToolsTransformPrefGroupUI.py b/flatcamGUI/preferences/tools/ToolsTransformPrefGroupUI.py index 60a8bf24..c3bffec1 100644 --- a/flatcamGUI/preferences/tools/ToolsTransformPrefGroupUI.py +++ b/flatcamGUI/preferences/tools/ToolsTransformPrefGroupUI.py @@ -1,7 +1,7 @@ from PyQt5 import QtWidgets from PyQt5.QtCore import QSettings -from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, EvalEntry2 +from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCEntry from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI import gettext @@ -191,7 +191,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI): "The 'x' in (x, y) will be used when using Flip on X and\n" "the 'y' in (x, y) will be used when using Flip on Y and") ) - self.flip_ref_entry = EvalEntry2("(0, 0)") + self.flip_ref_entry = FCEntry() grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2) grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2) diff --git a/flatcamTools/ToolTransform.py b/flatcamTools/ToolTransform.py index 1db4fb93..25a9b5f6 100644 --- a/flatcamTools/ToolTransform.py +++ b/flatcamTools/ToolTransform.py @@ -7,7 +7,7 @@ from PyQt5 import QtWidgets from FlatCAMTool import FlatCAMTool -from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, EvalEntry2 +from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, FCEntry import gettext import FlatCAMTranslation as fcTranslate @@ -300,7 +300,7 @@ class ToolTransform(FlatCAMTool): "The 'x' in (x, y) will be used when using Flip on X and\n" "the 'y' in (x, y) will be used when using Flip on Y.") ) - self.flip_ref_entry = EvalEntry2("(0, 0)") + self.flip_ref_entry = FCEntry() # self.flip_ref_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) # self.flip_ref_entry.setFixedWidth(70) @@ -533,7 +533,7 @@ class ToolTransform(FlatCAMTool): if self.app.defaults["tools_transform_mirror_point"]: self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"]) else: - self.flip_ref_entry.set_value((0, 0)) + self.flip_ref_entry.set_value("0, 0") if self.app.defaults["tools_transform_buffer_dis"]: self.buffer_entry.set_value(self.app.defaults["tools_transform_buffer_dis"]) From ee69744d6e656429e0ecbf51ae23069800946fca Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 8 May 2020 22:01:18 +0300 Subject: [PATCH 4/5] - removed the Apply theme button in the Preferences; it is now replaced by the more general buttons (either Save or Apply) - added a confirmation/warning message when applying a new theme --- CHANGELOG.md | 2 + .../preferences/PreferencesUIManager.py | 37 ++++++++++++++++++- .../general/GeneralGUIPrefGroupUI.py | 33 ++++------------- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b5fd6f..b83a0df5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ CHANGELOG for FlatCAM beta - 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 - optimized and fixed some issues in the self.on_toggle_units() method - the Exclusion areas will have all the orange color but the color of the outline will differ according to the type of the object from where it was added (cosmetic use only as the Exclusion areas will be applied globally) +- removed the Apply theme button in the Preferences; it is now replaced by the more general buttons (either Save or Apply) +- added a confirmation/warning message when applying a new theme 7.05.2020 diff --git a/flatcamGUI/preferences/PreferencesUIManager.py b/flatcamGUI/preferences/PreferencesUIManager.py index 14fa8762..c49841ba 100644 --- a/flatcamGUI/preferences/PreferencesUIManager.py +++ b/flatcamGUI/preferences/PreferencesUIManager.py @@ -913,7 +913,39 @@ class PreferencesUIManager: # make sure we update the self.current_defaults dict used to undo changes to self.defaults self.defaults.current_defaults.update(self.defaults) - if save_to_file: + # deal with theme change + theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + if theme_settings.contains("theme"): + theme = theme_settings.value('theme', type=str) + else: + theme = 'white' + + should_restart = False + val = self.ui.general_defaults_form.general_gui_group.theme_radio.get_value() + if val != theme: + msgbox = QtWidgets.QMessageBox() + msgbox.setText(_("Are you sure you want to continue?")) + msgbox.setWindowTitle(_("Application restart")) + msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/warning.png')) + + bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole) + msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.NoRole) + + msgbox.setDefaultButton(bt_yes) + msgbox.exec_() + response = msgbox.clickedButton() + + if response == bt_yes: + theme_settings.setValue('theme', val) + + # This will write the setting to the platform specific storage. + del theme_settings + + should_restart = True + else: + self.ui.general_defaults_form.general_gui_group.theme_radio.set_value(theme) + + if save_to_file or should_restart is True: self.save_defaults(silent=False) # load the defaults so they are updated into the app self.defaults.load(filename=os.path.join(self.data_path, 'current_defaults.FlatConfig')) @@ -951,6 +983,9 @@ class PreferencesUIManager: self.ui.plot_tab_area.closeTab(idx) break + if should_restart is True: + self.ui.app.on_app_restart() + def on_pref_close_button(self): # Preferences saved, update flag self.preferences_changed_flag = False diff --git a/flatcamGUI/preferences/general/GeneralGUIPrefGroupUI.py b/flatcamGUI/preferences/general/GeneralGUIPrefGroupUI.py index d71fab4c..baedacc7 100644 --- a/flatcamGUI/preferences/general/GeneralGUIPrefGroupUI.py +++ b/flatcamGUI/preferences/general/GeneralGUIPrefGroupUI.py @@ -56,13 +56,13 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): ) grid0.addWidget(self.gray_icons_cb, 1, 0, 1, 3) - self.theme_button = FCButton(_("Apply Theme")) - self.theme_button.setToolTip( - _("Select a theme for FlatCAM.\n" - "It will theme the plot area.\n" - "The application will restart after change.") - ) - grid0.addWidget(self.theme_button, 2, 0, 1, 3) + # self.theme_button = FCButton(_("Apply Theme")) + # self.theme_button.setToolTip( + # _("Select a theme for FlatCAM.\n" + # "It will theme the plot area.\n" + # "The application will restart after change.") + # ) + # grid0.addWidget(self.theme_button, 2, 0, 1, 3) separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.HLine) @@ -381,8 +381,6 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): self.layout.addStretch() - self.theme_button.clicked.connect(self.on_theme_change) - # ############################################################################# # ############################# GUI COLORS SIGNALS ############################ # ############################################################################# @@ -418,23 +416,6 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): self.layout_combo.activated.connect(self.on_layout) - def on_theme_change(self): - val = self.theme_radio.get_value() - - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") - if theme_settings.contains("theme"): - theme = theme_settings.value('theme', type=str) - else: - theme = 'white' - - if val != theme: - theme_settings.setValue('theme', val) - - # This will write the setting to the platform specific storage. - del theme_settings - - self.app.on_app_restart() - @staticmethod def handle_style(style): # set current style From 0e07ea15418f71dfe1005c04acf9fb9e65f3a34a Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 9 May 2020 05:18:05 +0300 Subject: [PATCH 5/5] - 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 --- CHANGELOG.md | 5 ++ FlatCAMApp.py | 20 +++++--- FlatCAMCommon.py | 58 +++++++++++++++++++++- flatcamGUI/ObjectUI.py | 80 ++++++++++++++++++++----------- flatcamObjects/FlatCAMGeometry.py | 79 +++++++++++++++++++++++++++++- flatcamObjects/FlatCAMObj.py | 6 +-- 6 files changed, 207 insertions(+), 41 deletions(-) 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): """