From 8fc916b746c675c497fc8f4d231cfabe43691a02 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 4 Apr 2019 00:47:08 +0300 Subject: [PATCH] - working on Gerber Editor - added the key shortcuts: wip - made saving of the project file non-blocking and also while saving the project file, if the user tries again to close the app while project file is being saved, the app will close only after saving is complete (the project file size is non zero) --- FlatCAMApp.py | 53 ++++++-- README.md | 4 +- flatcamEditors/FlatCAMGrbEditor.py | 4 +- flatcamGUI/FlatCAMGUI.py | 188 +++++++++++++++++++++++++++-- 4 files changed, 230 insertions(+), 19 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 4ef7f02f..6da54c14 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -11,6 +11,7 @@ import getopt import random import simplejson as json import lzma +import threading from stat import S_IREAD, S_IRGRP, S_IROTH import subprocess @@ -112,12 +113,13 @@ class App(QtCore.QObject): manual_url = "http://flatcam.org/manual/index.html" video_url = "https://www.youtube.com/playlist?list=PLVvP2SYRpx-AQgNlfoxw93tXUXon7G94_" - should_we_quit = True - # this variable will hold the project status # if True it will mean that the project was modified and not saved should_we_save = False + # flag is True if saving action has been triggered + save_in_progress = False + ################## ## Signals ## ################## @@ -127,6 +129,8 @@ class App(QtCore.QObject): # * App.info() --> Print on the status bar inform = QtCore.pyqtSignal(str) + app_quit = QtCore.pyqtSignal() + # General purpose background task worker_task = QtCore.pyqtSignal(dict) @@ -1224,6 +1228,7 @@ class App(QtCore.QObject): ### Signal handling ### ## Custom signals self.inform.connect(self.info) + self.app_quit.connect(self.quit_application) self.message.connect(self.message_dialog) self.progress.connect(self.set_progress_bar) self.object_created.connect(self.on_object_created) @@ -3164,6 +3169,11 @@ class App(QtCore.QObject): self.inform.emit(_("Factory defaults saved.")) def final_save(self): + + if self.save_in_progress: + self.inform.emit(_("Application is saving the project. Please wait ...")) + return + if self.should_we_save and self.collection.get_list(): msgbox = QtWidgets.QMessageBox() msgbox.setText(_("There are files/objects modified in FlatCAM. " @@ -3178,11 +3188,15 @@ class App(QtCore.QObject): response = msgbox.exec_() if response == QtWidgets.QMessageBox.Yes: - self.on_file_saveprojectas(thread=False) + self.on_file_saveprojectas(thread=True, quit=True) + elif response == QtWidgets.QMessageBox.No: + QtWidgets.qApp.quit() elif response == QtWidgets.QMessageBox.Cancel: - self.should_we_quit = False return + else: + QtWidgets.qApp.quit() + def quit_application(self): self.save_defaults() log.debug("App.final_save() --> App Defaults saved.") @@ -6218,7 +6232,7 @@ class App(QtCore.QObject): self.should_we_save = False - def on_file_saveprojectas(self, make_copy=False, thread=True): + def on_file_saveprojectas(self, make_copy=False, thread=True, quit=False): """ Callback for menu item File->Save Project As... Opens a file chooser and saves the project to the given file via @@ -6253,9 +6267,9 @@ class App(QtCore.QObject): if thread is True: self.worker_task.emit({'fcn': self.save_project, - 'params': [filename]}) + 'params': [filename, quit]}) else: - self.save_project(filename) + self.save_project(filename, quit) # self.save_project(filename) self.file_opened.emit("project", filename) @@ -7861,7 +7875,7 @@ The normal flow when working in FlatCAM is the following:

for obj in objects: obj.on_generatecnc_button_click() - def save_project(self, filename): + def save_project(self, filename, quit=False): """ Saves the current project to the specified file. @@ -7871,6 +7885,8 @@ The normal flow when working in FlatCAM is the following:

""" self.log.debug("save_project()") + self.save_in_progress = True + with self.proc_container.new(_("Saving FlatCAM Project")) as proc: ## Capture the latest changes # Current object @@ -7927,6 +7943,27 @@ The normal flow when working in FlatCAM is the following:

else: self.inform.emit(_("[ERROR_NOTCL] Failed to save project file: %s. Retry to save it.") % filename) + if quit: + t = threading.Thread(target=lambda: self.check_project_file_size(1, filename=filename)) + t.start() + + # using Alfe's answer from here: + # https://stackoverflow.com/questions/474528/what-is-the-best-way-to-repeatedly-execute-a-function-every-x-seconds-in-python + def check_project_file_size(self, delay, filename): + next_time = time.time() + delay + while True: + time.sleep(max(0, next_time - time.time())) + try: + statinfo = os.stat(filename) + if statinfo: + self.app_quit.emit() + except Exception: + traceback.print_exc() + # in production code you might want to have this instead of course: + # logger.exception("Problem while executing repetitive task.") + # skip tasks if we are behind schedule: + next_time += (time.time() - next_time) // delay * delay + delay + def on_options_app2project(self): """ Callback for Options->Transfer Options->App=>Project. Copies options diff --git a/README.md b/README.md index 5b113ae2..49fff54a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,9 @@ CAD program, and create G-Code for Isolation routing. - fixed plotting in Gerber Editor - working on GUI in Gerber Editor - added a Gcode end_command: default is M02 -- modified the calling of the editor2object() slot function +- modified the calling of the editor2object() slot function to fix an issue with updating geometry imported from SVG file, after edut +- working on Gerber Editor - added the key shortcuts: wip +- made saving of the project file non-blocking and also while saving the project file, if the user tries again to close the app while project file is being saved, the app will close only after saving is complete (the project file size is non zero) 31.03.2019 diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index 96b959bd..65d5cd6d 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -1365,7 +1365,7 @@ class FlatCAMGrbEditor(QtCore.QObject): else: self.edited_obj_name += "_edit" - self.app.worker_task.emit({'fcn': self.new_edited_excellon, + self.app.worker_task.emit({'fcn': self.new_edited_gerber, 'params': [self.edited_obj_name]}) if self.gerber_obj.slots: @@ -1403,7 +1403,7 @@ class FlatCAMGrbEditor(QtCore.QObject): obj.options = {} return True - def new_edited_excellon(self, outname): + def new_edited_gerber(self, outname): """ Creates a new Excellon object for the edited Excellon. Thread-safe. diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index e6c92393..9b86769a 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -2383,6 +2383,177 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # Show Shortcut list if key == 'F3': self.app.on_shortcut_list() + elif self.app.call_source == 'grb_editor': + if modifiers == QtCore.Qt.ControlModifier: + # save (update) the current geometry and return to the App + if key == QtCore.Qt.Key_S or key == 'S': + self.app.editor2object() + return + + # toggle the measurement tool + if key == QtCore.Qt.Key_M or key == 'M': + self.app.measurement_tool.run() + return + + elif modifiers == QtCore.Qt.ShiftModifier: + pass + elif modifiers == QtCore.Qt.AltModifier: + pass + elif modifiers == QtCore.Qt.NoModifier: + # Abort the current action + if key == QtCore.Qt.Key_Escape or key == 'Escape': + # self.on_tool_select("select") + self.app.inform.emit(_("[WARNING_NOTCL] Cancelled.")) + + self.app.grb_editor.delete_utility_geometry() + + self.app.grb_editor.replot() + # self.select_btn.setChecked(True) + # self.on_tool_select('select') + self.app.grb_editor.select_tool('select') + return + + # Delete selected object if delete key event comes out of canvas + if key == 'Delete': + self.app.grb_editor.launched_from_shortcuts = True + if self.app.grb_editor.selected: + self.app.grb_editor.delete_selected() + self.app.grb_editor.replot() + else: + self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to delete.")) + return + + # Delete aperture in apertures table if delete key event comes from the Selected Tab + if key == QtCore.Qt.Key_Delete: + self.app.grb_editor.launched_from_shortcuts = True + self.app.grb_editor.on_tool_delete() + return + + if key == QtCore.Qt.Key_Minus or key == '-': + self.app.grb_editor.launched_from_shortcuts = True + self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], + [self.app.grb_editor.snap_x, self.app.grb_editor.snap_y]) + return + + if key == QtCore.Qt.Key_Equal or key == '=': + self.app.grb_editor.launched_from_shortcuts = True + self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], + [self.app.grb_editor.snap_x, self.app.grb_editor.snap_y]) + return + + # toggle display of Notebook area + if key == QtCore.Qt.Key_QuoteLeft or key == '`': + self.app.grb_editor.launched_from_shortcuts = True + self.app.on_toggle_notebook() + return + + # Switch to Project Tab + if key == QtCore.Qt.Key_1 or key == '1': + self.app.grb_editor.launched_from_shortcuts = True + self.app.on_select_tab('project') + return + + # Switch to Selected Tab + if key == QtCore.Qt.Key_2 or key == '2': + self.app.grb_editor.launched_from_shortcuts = True + self.app.on_select_tab('selected') + return + + # Switch to Tool Tab + if key == QtCore.Qt.Key_3 or key == '3': + self.app.grb_editor.launched_from_shortcuts = True + self.app.on_select_tab('tool') + return + + # Copy + if key == QtCore.Qt.Key_C or key == 'C': + self.app.grb_editor.launched_from_shortcuts = True + if self.app.grb_editor.selected: + self.app.inform.emit(_("Click on target point.")) + self.app.ui.copy_aperture_btn.setChecked(True) + self.app.grb_editor.on_tool_select('aperture_copy') + self.app.grb_editor.active_tool.set_origin( + (self.app.grb_editor.snap_x, self.app.grb_editor.snap_y)) + else: + self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to copy.")) + return + + # Add Aperture Tool + if key == QtCore.Qt.Key_A or key == 'A': + self.app.grb_editor.launched_from_shortcuts = True + self.app.inform.emit(_("Click on target point.")) + self.app.ui.add_aperture_btn.setChecked(True) + + self.app.grb_editor.x = self.app.mouse[0] + self.app.grb_editor.y = self.app.mouse[1] + + self.app.grb_editor.select_tool('aperture_add') + return + + # Grid Snap + if key == QtCore.Qt.Key_G or key == 'G': + self.app.grb_editor.launched_from_shortcuts = True + # make sure that the cursor shape is enabled/disabled, too + if self.app.grb_editor.options['grid_snap'] is True: + self.app.app_cursor.enabled = False + else: + self.app.app_cursor.enabled = True + self.app.ui.grid_snap_btn.trigger() + return + + # Jump to coords + if key == QtCore.Qt.Key_J or key == 'J': + self.app.on_jump_to() + + # Corner Snap + if key == QtCore.Qt.Key_K or key == 'K': + self.app.grb_editor.launched_from_shortcuts = True + self.app.ui.corner_snap_btn.trigger() + return + + # Move + if key == QtCore.Qt.Key_M or key == 'M': + self.app.grb_editor.launched_from_shortcuts = True + if self.app.grb_editor.selected: + self.app.inform.emit(_("Click on target point.")) + self.app.ui.move_aperture_btn.setChecked(True) + self.app.exc_editor.on_tool_select('aperture_move') + self.app.grb_editor.active_tool.set_origin( + (self.app.grb_editor.snap_x, self.app.grb_editor.snap_y)) + else: + self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to move.")) + return + + # Resize Tool + if key == QtCore.Qt.Key_R or key == 'R': + self.app.grb_editor.launched_from_shortcuts = True + self.app.grb_editor.select_tool('drill_resize') + return + + # Add Track + if key == QtCore.Qt.Key_T or key == 'T': + self.app.grb_editor.launched_from_shortcuts = True + ## Current application units in Upper Case + self.units = self.general_defaults_group.general_app_group.units_radio.get_value().upper() + return + + # Zoom Fit + if key == QtCore.Qt.Key_V or key == 'V': + self.app.grb_editor.launched_from_shortcuts = True + self.app.on_zoom_fit(None) + return + + # Propagate to tool + response = None + if self.app.grb_editor.active_tool is not None: + response = self.app.grb_editor.active_tool.on_key(key=key) + if response is not None: + self.app.inform.emit(response) + + # Show Shortcut list + if key == QtCore.Qt.Key_F3 or key == 'F3': + self.app.on_shortcut_list() + return elif self.app.call_source == 'exc_editor': if modifiers == QtCore.Qt.ControlModifier: # save (update) the current geometry and return to the App @@ -2640,16 +2811,17 @@ class FlatCAMGUI(QtWidgets.QMainWindow): event.ignore() def closeEvent(self, event): - grect = self.geometry() + if self.app.save_in_progress: + self.app.inform.emit(_("[WARNING_NOTCL] Application is saving the project. Please wait ...")) + else: + grect = self.geometry() - # self.splitter.sizes()[0] is actually the size of the "notebook" - if not self.isMaximized(): - self.geom_update.emit(grect.x(), grect.y(), grect.width(), grect.height(), self.splitter.sizes()[0]) + # self.splitter.sizes()[0] is actually the size of the "notebook" + if not self.isMaximized(): + self.geom_update.emit(grect.x(), grect.y(), grect.width(), grect.height(), self.splitter.sizes()[0]) - self.final_save.emit() - - if self.app.should_we_quit is False: - event.ignore() + self.final_save.emit() + event.ignore() class GeneralPreferencesUI(QtWidgets.QWidget):