diff --git a/CHANGELOG.md b/CHANGELOG.md index 18650c9e..d71bdb7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM beta ================================================= +26.12.2020 + +- made sure that the Tool menu is disabled when entering into an Editor and re-enabled on Editor exit +- Gerber Editor - working on a new sub-tool: Import Shape which should allow importing polygons from other Gerber objects + 25.12.2020 - merged PR by Dmitriy Klabukov and expanded it a bit diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index f2b551db..c0930103 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -2594,6 +2594,10 @@ class SelectEditorGrb(QtCore.QObject, DrawTool): pass self.draw_app.plot_object.connect(self.after_selection) + # if the shapes are not visible make them visible + if self.draw_app.visible is False: + self.draw_app.visible = True + def set_origin(self, origin): self.origin = origin @@ -2720,6 +2724,310 @@ class SelectEditorGrb(QtCore.QObject, DrawTool): self.draw_app.plot_all() +class ImportEditorGrb(QtCore.QObject, DrawTool): + import_signal = QtCore.pyqtSignal(object) + + def __init__(self, draw_app): + super().__init__(draw_app=draw_app) + # DrawTool.__init__(self, draw_app) + self.name = 'import' + self.origin = None + + self.draw_app = draw_app + self.storage = self.draw_app.storage_dict + # self.selected = self.draw_app.selected + + # here we store all shapes that were selected + self.sel_storage = [] + + # since SelectEditorGrb tool is activated whenever a tool is exited I place here the reinitialization of the + # bending modes using in RegionEditorGrb and TrackEditorGrb + self.draw_app.bend_mode = 1 + + # here store the selected apertures + self.sel_aperture = set() + + # multiprocessing results + self.results = [] + + self.mp = None + self.mr = None + self.mm = None + self.app = self.draw_app.app + self.canvas = self.draw_app.canvas + + self.x = None + self.y = None + self.pos = None + self.snap_x = None + self.snap_y = None + + try: + self.draw_app.ui.apertures_table.clearSelection() + except Exception as e: + log.error("FlatCAMGerbEditor.ImportEditorGrb.__init__() --> %s" % str(e)) + + self.draw_app.hide_tool('all') + self.draw_app.ui.array_frame.hide() + + try: + QtGui.QGuiApplication.restoreOverrideCursor() + except Exception as e: + log.debug("AppGerberEditor.ImportEditorGrb --> %s" % str(e)) + + try: + self.import_signal.disconnect() + except (TypeError, AttributeError): + pass + self.import_signal.connect(self.import_shapes) + + try: + self.draw_app.plot_object.disconnect() + except (TypeError, AttributeError): + pass + self.draw_app.plot_object.connect(self.plot_import) + + self.import_connect() + + self.draw_app.visible = False + + # disengage the grid snapping since it may be hard to click on polygons with grid snapping on + if self.app.ui.grid_snap_btn.isChecked(): + self.grid_status_memory = True + self.app.ui.grid_snap_btn.trigger() + else: + self.grid_status_memory = False + + def import_connect(self): + # first connect to new, then disconnect the old handlers + # don't ask why but if there is nothing connected I've seen issues + self.mp = self.canvas.graph_event_connect('mouse_press', self.on_mouse_click) + self.mm = self.canvas.graph_event_connect('mouse_move', self.on_mouse_move) + self.mr = self.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release) + + # disconnect old + if self.app.is_legacy is False: + self.canvas.graph_event_disconnect('mouse_press', self.draw_app.on_canvas_click) + self.canvas.graph_event_disconnect('mouse_move', self.draw_app.on_canvas_move) + self.canvas.graph_event_disconnect('mouse_release', self.draw_app.on_canvas_click_release) + else: + self.canvas.graph_event_disconnect(self.draw_app.mp) + self.canvas.graph_event_disconnect(self.draw_app.mm) + self.canvas.graph_event_disconnect(self.draw_app.mr) + + def import_disconnect(self): + self.draw_app.mp = self.canvas.graph_event_connect('mouse_press', self.draw_app.on_canvas_click) + self.draw_app.mm = self.canvas.graph_event_connect('mouse_move', self.draw_app.on_canvas_move) + self.draw_app.mr = self.canvas.graph_event_connect('mouse_release', self.draw_app.on_canvas_click_release) + + if self.app.is_legacy is False: + self.canvas.graph_event_disconnect('mouse_press', self.on_mouse_click) + self.canvas.graph_event_disconnect('mouse_move', self.on_mouse_move) + self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) + else: + self.canvas.graph_event_disconnect(self.mp) + self.canvas.graph_event_disconnect(self.mm) + self.canvas.graph_event_disconnect(self.mr) + + def on_mouse_click(self, event): + if self.app.is_legacy is False: + event_pos = event.pos + else: + event_pos = (event.xdata, event.ydata) + + # update click position (used also in self.on_mouse_move() ) + self.pos = self.canvas.translate_coords(event_pos) + if self.app.grid_status(): + self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1]) + else: + self.pos = (self.pos[0], self.pos[1]) + + if event.button == 1: + self.app.ui.rel_position_label.setText("Dx: %.4f   Dy: " + "%.4f    " % (0, 0)) + + def on_mouse_move(self, event): + if not self.app.plotcanvas.native.hasFocus(): + self.app.plotcanvas.native.setFocus() + + if self.app.is_legacy is False: + event_pos = event.pos + self.event_is_dragging = event.is_dragging + right_button = 2 + else: + event_pos = (event.xdata, event.ydata) + self.event_is_dragging = self.app.plotcanvas.is_dragging + right_button = 3 + + pos_canvas = self.canvas.translate_coords(event_pos) + event.xdata, event.ydata = pos_canvas[0], pos_canvas[1] + + self.x = event.xdata + self.y = event.ydata + + self.app.ui.popMenu.mouse_is_panning = False + + # if the RMB is clicked and mouse is moving over plot then 'panning_action' is True + if event.button == right_button and self.event_is_dragging == 1: + self.app.ui.popMenu.mouse_is_panning = True + return + + try: + x = float(event.xdata) + y = float(event.ydata) + except TypeError: + return + + # Snap coordinates + if self.app.grid_status(): + x, y = self.app.geo_editor.snap(x, y) + + # Update cursor + self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color=self.app.cursor_color_3D, + edge_width=self.app.defaults["global_cursor_width"], + size=self.app.defaults["global_cursor_size"]) + + self.snap_x = x + self.snap_y = y + + self.app.mouse = [x, y] + + if self.pos is None: + self.pos = (0, 0) + self.app.dx = x - self.pos[0] + self.app.dy = y - self.pos[1] + + # # update the position label in the infobar since the APP mouse event handlers are disconnected + self.app.ui.position_label.setText(" X: %.4f   " + "Y: %.4f " % (x, y)) + + # update the reference position label in the infobar since the APP mouse event handlers are disconnected + self.app.ui.rel_position_label.setText("Dx: %.4f   Dy: " + "%.4f    " % (self.app.dx, self.app.dy)) + + # Selection area on canvas section # ## + if self.event_is_dragging == 1 and event.button == 1: + dx = pos_canvas[0] - self.pos[0] + self.app.delete_selection_shape() + if dx < 0: + self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x, y), + color=self.app.defaults["global_alt_sel_line"], + face_color=self.app.defaults['global_alt_sel_fill']) + self.app.selection_type = False + else: + self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x, y)) + self.app.selection_type = True + else: + self.app.selection_type = None + + def on_mouse_click_release(self, event): + left_button = 1 + right_button = 2 if self.app.is_legacy is False else 3 + + event_pos = event.pos if self.app.is_legacy is False else (event.xdata, event.ydata) + try: + x = float(event_pos[0]) + y = float(event_pos[1]) + except TypeError: + return + + event_pos = (x, y) + curr_pos = self.app.plotcanvas.translate_coords(event_pos) + if self.app.grid_status(): + curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1]) + else: + curr_pos = (curr_pos[0], curr_pos[1]) + + try: + if event.button == left_button: + if self.app.selection_type is not None: + self.draw_app.app.delete_selection_shape() + self.app.selection_type = None + self.selection_area_handler(self.pos, curr_pos, self.app.selection_type) + else: + self.select_handler(curr_pos) + elif event.button == right_button: # right click + if self.event_is_dragging is False: + # restore the Grid snapping if it was active before + if self.grid_status_memory is True: + self.app.ui.grid_snap_btn.trigger() + + self.import_disconnect() + + # disconnect flags + self.app.tool_shapes.clear(update=True) + + self.import_shapes() + self.app.inform.emit('[success] %s' % _("Done.")) + self.draw_app.select_tool('select') + + except Exception as e: + self.app.log.warning("ImportEditorGrb.on_mouse_click_release() RMB click --> Error: %s" % str(e)) + raise + + def select_handler(self, pos): + """ + + :param pos: mouse click position + :type pos: tuple + :return: None + :rtype: None + """ + added_poly_count = 0 + + for obj in self.app.collection.get_list(): + if obj.kind == 'gerber': + for apid in obj.apertures: + if 'geometry' in obj.apertures[apid]: + for geo_el in obj.apertures[apid]['geometry']: + if 'solid' in geo_el: + solid_geo = geo_el['solid'] + if Point(pos).within(solid_geo): + shape_id = self.app.tool_shapes.add(tolerance=obj.drawing_tolerance, layer=0, + shape=solid_geo, + color=self.app.defaults[ + 'global_sel_draw_color'] + 'AF', + face_color=self.app.defaults[ + 'global_sel_draw_color'] + 'AF', + visible=True) + added_poly_count += 1 + + if added_poly_count > 0: + self.app.tool_shapes.redraw() + self.app.inform.emit( + '%s: %d. %s' % (_("Added polygon"), + int(added_poly_count), + _("Click to add next polygon or right click to start.")) + ) + else: + self.app.inform.emit(_("No polygon in selection.")) + + def selection_area_handler(self, start_pos, end_pos, selection_type): + """ + + :param start_pos: mouse selection start position + :type start_pos: tuple + :param end_pos: mouse selection end position + :type end_pos: tuple + :param selection_type: True if selection is left-to-tight mouse drag, False if right-to-left mouse drag + :type selection_type: bool + :return: None + :rtype: None + """ + + poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])]) + + + def import_shapes(self): + print("shapes added") + + def plot_import(self): + self.draw_app.plot_all() + + def clean_up(self): + self.draw_app.plot_all() + + class TransformEditorGrb(ShapeToolEditorGrb): def __init__(self, draw_app): ShapeToolEditorGrb.__init__(self, draw_app) @@ -2853,7 +3161,7 @@ class AppGerberEditor(QtCore.QObject): self.launched_from_shortcuts = False def_tol_val = float(self.app.defaults["global_tolerance"]) - self.tolerance = def_tol_val if self.units == 'MM' else def_tol_val / 20 + self.tolerance = def_tol_val if self.units == 'MM' else def_tol_val / 25.4 # options of this widget (AppGerberEditor class is a widget) self.options = { @@ -2990,6 +3298,7 @@ class AppGerberEditor(QtCore.QObject): "buffer": {"button": self.app.ui.aperture_buffer_btn, "constructor": BufferEditorGrb}, "scale": {"button": self.app.ui.aperture_scale_btn, "constructor": ScaleEditorGrb}, "markarea": {"button": self.app.ui.aperture_markarea_btn, "constructor": MarkEditorGrb}, + "import": {"button": self.app.ui.grb_import_btn, "constructor": ImportEditorGrb}, "eraser": {"button": self.app.ui.aperture_eraser_btn, "constructor": EraserEditorGrb}, "copy": {"button": self.app.ui.aperture_copy_btn, "constructor": CopyEditorGrb}, "transform": {"button": self.app.ui.grb_transform_btn, "constructor": TransformEditorGrb}, @@ -4693,12 +5002,13 @@ class AppGerberEditor(QtCore.QObject): except Exception as e: self.app.log.debug("AppGerberEditor.on_grb_click_release() --> %s" % str(e)) - if self.active_tool.complete is False and not isinstance(self.active_tool, SelectEditorGrb): - self.active_tool.complete = True - self.in_action = False - self.delete_utility_geometry() - self.app.inform.emit('[success] %s' % _("Done.")) - self.select_tool('select') + if self.active_tool.complete is False: + if not isinstance(self.active_tool, SelectEditorGrb): + self.active_tool.complete = True + self.in_action = False + self.delete_utility_geometry() + self.app.inform.emit('[success] %s' % _("Done.")) + self.select_tool('select') else: self.app.cursor = QtGui.QCursor() self.app.populate_cmenu_grids() @@ -5026,6 +5336,37 @@ class AppGerberEditor(QtCore.QObject): color = color[:7] + 'AF' self.shapes.add(shape=geometry, color=color, face_color=color, layer=0, tolerance=self.tolerance) + @property + def visible(self): + return self.shapes.visible + + @visible.setter + def visible(self, value, threaded=True): + log.debug("FlatCAMObj.visible()") + + current_visibility = self.shapes.visible + + # self.shapes.visible = value # maybe this is slower in VisPy? use enabled property? + + def task(visibility): + if visibility is True: + if value is False: + self.shapes.visible = False + else: + if value is True: + self.shapes.visible = True + + # Not all object types has annotations + try: + self.ma_annotation.visible = value + except Exception: + pass + + if threaded: + self.app.worker_task.emit({'fcn': task, 'params': [current_visibility]}) + else: + task(current_visibility) + # def start_delayed_plot(self, check_period): # """ # This function starts an QTImer and it will periodically check if all the workers finish the plotting functions diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 569eb3a8..9b55aba1 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -1251,6 +1251,8 @@ class MainGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/scale32.png'), _('Scale')) self.aperture_markarea_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/markarea32.png'), _('Mark Area')) + self.grb_import_btn = self.grb_edit_toolbar.addAction( + QtGui.QIcon(self.app.resource_location + '/import.png'), _('Import Shape')) self.aperture_eraser_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/eraser26.png'), _('Eraser')) @@ -2458,6 +2460,9 @@ class MainGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/scale32.png'), _('Scale')) self.aperture_markarea_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/markarea32.png'), _('Mark Area')) + self.grb_import_btn = self.grb_edit_toolbar.addAction( + QtGui.QIcon(self.app.resource_location + '/import.png'), _('Import Shape')) + self.aperture_eraser_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/eraser26.png'), _('Eraser')) diff --git a/appTools/ToolIsolation.py b/appTools/ToolIsolation.py index e990da56..40fe96cd 100644 --- a/appTools/ToolIsolation.py +++ b/appTools/ToolIsolation.py @@ -1585,11 +1585,11 @@ class ToolIsolation(AppTool, Gerber): if self.ui.valid_cb.get_value() is True: self.find_safe_tooldia_multiprocessing() - def worker_task(iso_obj): - with self.app.proc_container.new('%s ...' % _("Isolating")): - self.isolate_handler(iso_obj) + def worker_task(iso_class): + with iso_class.app.proc_container.new('%s ...' % _("Isolating")): + iso_class.isolate_handler(iso_class.grb_obj) - self.app.worker_task.emit({'fcn': worker_task, 'params': [self.grb_obj]}) + self.app.worker_task.emit({'fcn': worker_task, 'params': [self]}) def isolate_handler(self, isolated_obj): """ diff --git a/app_Main.py b/app_Main.py index 9d7e3462..22dd2517 100644 --- a/app_Main.py +++ b/app_Main.py @@ -2443,8 +2443,6 @@ class App(QtCore.QObject): :return: None """ self.defaults.report_usage("object2editor()") - # disable the objects menu as it may interfere with the appEditors - self.ui.menuobjects.setDisabled(True) edited_object = self.collection.get_active() @@ -2552,9 +2550,6 @@ class App(QtCore.QObject): self.gcode_editor.edit_fcgcode(edited_object) - # make sure that we can't select another object while in Editor Mode: - self.ui.project_frame.setDisabled(True) - for idx in range(self.ui.notebook.count()): # store the Properties Tab text color here and change the color and text if self.ui.notebook.tabText(idx) == _("Properties"): @@ -2577,6 +2572,13 @@ class App(QtCore.QObject): else: self.old_state_of_tools_toolbar = False + # make sure that we can't select another object while in Editor Mode: + self.ui.project_frame.setDisabled(True) + # disable the objects menu as it may interfere with the appEditors + self.ui.menuobjects.setDisabled(True) + # disable the tools menu as it makes sense not to be available when in the Editor + self.ui.menutool.setDisabled(True) + self.ui.plot_tab_area.setTabText(0, _("EDITOR Area")) self.ui.plot_tab_area.protectTab(0) self.log.debug("######################### Starting the EDITOR ################################") @@ -2594,9 +2596,6 @@ class App(QtCore.QObject): """ self.defaults.report_usage("editor2object()") - # re-enable the objects menu that was disabled on entry in Editor mode - self.ui.menuobjects.setDisabled(False) - # do not update a geometry or excellon object unless it comes out of an editor if self.call_source == 'app': return @@ -2815,6 +2814,11 @@ class App(QtCore.QObject): if self.ui.notebook.tabText(idx) == _("Project"): self.ui.notebook.tabBar.setTabEnabled(idx, True) + # re-enable the objects menu that was disabled on entry in Editor mode + self.ui.menuobjects.setDisabled(False) + # re-enable the tool menu that was disabled on entry in Editor mode + self.ui.menutool.setDisabled(False) + # restore the call_source to app self.call_source = 'app' self.log.debug("######################### Closing the EDITOR ################################")