From 9e8ab610b44eba52400b39358f641ca1b93ff8de Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 22 Jul 2020 15:49:52 +0300 Subject: [PATCH] - working on a proper GCode Editor --- CHANGELOG.md | 4 + appEditors/AppTextEditor.py | 22 ++-- appEditors/appGCodeEditor.py | 199 +++++++++++++++++++++++++++++++++++ appGUI/GUIElements.py | 9 +- appGUI/ObjectUI.py | 2 +- appObjects/FlatCAMCNCJob.py | 21 +++- app_Main.py | 9 +- 7 files changed, 245 insertions(+), 21 deletions(-) create mode 100644 appEditors/appGCodeEditor.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6531b424..f57c81e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM beta ================================================= +22.07.2020 + +- working on a proper GCode Editor + 21.07.2020 - updated the FCRadio class with a method that allow disabling certain options diff --git a/appEditors/AppTextEditor.py b/appEditors/AppTextEditor.py index e9726c98..4efe000d 100644 --- a/appEditors/AppTextEditor.py +++ b/appEditors/AppTextEditor.py @@ -5,7 +5,7 @@ # MIT Licence # # ########################################################## -from appGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber +from appGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber, FCButton from PyQt5 import QtPrintSupport, QtWidgets, QtCore, QtGui from reportlab.platypus import SimpleDocTemplate, Paragraph @@ -30,6 +30,7 @@ class AppTextEditor(QtWidgets.QWidget): self.app = app self.plain_text = plain_text + self.callback = lambda x: None self.setSizePolicy( QtWidgets.QSizePolicy.MinimumExpanding, @@ -71,17 +72,17 @@ class AppTextEditor(QtWidgets.QWidget): if text: self.code_editor.setPlainText(text) - self.buttonPreview = QtWidgets.QPushButton(_('Print Preview')) + self.buttonPreview = FCButton(_('Print Preview')) self.buttonPreview.setIcon(QtGui.QIcon(self.app.resource_location + '/preview32.png')) self.buttonPreview.setToolTip(_("Open a OS standard Preview Print window.")) self.buttonPreview.setMinimumWidth(100) - self.buttonPrint = QtWidgets.QPushButton(_('Print Code')) + self.buttonPrint = FCButton(_('Print Code')) self.buttonPrint.setIcon(QtGui.QIcon(self.app.resource_location + '/printer32.png')) self.buttonPrint.setToolTip(_("Open a OS standard Print window.")) self.buttonPrint.setMinimumWidth(100) - self.buttonFind = QtWidgets.QPushButton(_('Find in Code')) + self.buttonFind = FCButton(_('Find in Code')) self.buttonFind.setIcon(QtGui.QIcon(self.app.resource_location + '/find32.png')) self.buttonFind.setToolTip(_("Will search and highlight in yellow the string in the Find box.")) self.buttonFind.setMinimumWidth(100) @@ -89,7 +90,7 @@ class AppTextEditor(QtWidgets.QWidget): self.entryFind = FCEntry() self.entryFind.setToolTip(_("Find box. Enter here the strings to be searched in the text.")) - self.buttonReplace = QtWidgets.QPushButton(_('Replace With')) + self.buttonReplace = FCButton(_('Replace With')) self.buttonReplace.setIcon(QtGui.QIcon(self.app.resource_location + '/replace32.png')) self.buttonReplace.setToolTip(_("Will replace the string from the Find box with the one in the Replace box.")) self.buttonReplace.setMinimumWidth(100) @@ -101,22 +102,22 @@ class AppTextEditor(QtWidgets.QWidget): self.sel_all_cb.setToolTip(_("When checked it will replace all instances in the 'Find' box\n" "with the text in the 'Replace' box..")) - self.button_copy_all = QtWidgets.QPushButton(_('Copy All')) + self.button_copy_all = FCButton(_('Copy All')) self.button_copy_all.setIcon(QtGui.QIcon(self.app.resource_location + '/copy_file32.png')) self.button_copy_all.setToolTip(_("Will copy all the text in the Code Editor to the clipboard.")) self.button_copy_all.setMinimumWidth(100) - self.buttonOpen = QtWidgets.QPushButton(_('Open Code')) + self.buttonOpen = FCButton(_('Open Code')) self.buttonOpen.setIcon(QtGui.QIcon(self.app.resource_location + '/folder32_bis.png')) self.buttonOpen.setToolTip(_("Will open a text file in the editor.")) self.buttonOpen.setMinimumWidth(100) - self.buttonSave = QtWidgets.QPushButton(_('Save Code')) + self.buttonSave = FCButton(_('Save Code')) self.buttonSave.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png')) self.buttonSave.setToolTip(_("Will save the text in the editor into a file.")) self.buttonSave.setMinimumWidth(100) - self.buttonRun = QtWidgets.QPushButton(_('Run Code')) + self.buttonRun = FCButton(_('Run Code')) self.buttonRun.setToolTip(_("Will run the TCL commands found in the text file, one by one.")) self.buttonRun.setMinimumWidth(100) @@ -162,6 +163,9 @@ class AppTextEditor(QtWidgets.QWidget): self.code_edited = '' + def set_callback(self, callback): + self.callback = callback + def handlePrint(self): self.app.defaults.report_usage("handlePrint()") diff --git a/appEditors/appGCodeEditor.py b/appEditors/appGCodeEditor.py new file mode 100644 index 00000000..61a4e0f2 --- /dev/null +++ b/appEditors/appGCodeEditor.py @@ -0,0 +1,199 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# File Author: Marius Adrian Stanciu (c) # +# Date: 07/22/2020 # +# MIT Licence # +# ########################################################## + +from appEditors.AppTextEditor import AppTextEditor +from appObjects import FlatCAMCNCJob +from appGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber, FCButton +from PyQt5 import QtWidgets, QtCore, QtGui + +# from io import StringIO + +import logging + +import gettext +import appTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + +log = logging.getLogger('base') + + +class appGCodeEditor(QtCore.QObject): + + def __init__(self, app, parent=None): + super().__init__(parent=parent) + + self.app = app + self.plain_text = '' + self.callback = lambda x: None + + self.ui = appGCodeEditorUI(app=self.app) + + # ################################################################################# + # ################### SIGNALS ##################################################### + # ################################################################################# + + self.gcode_obj = None + self.code_edited = '' + + def set_ui(self): + pass + + def build_ui(self): + pass + + def ui_connect(self): + pass + + def ui_disconnect(self): + pass + + def handleTextChanged(self): + # enable = not self.ui.code_editor.document().isEmpty() + # self.ui.buttonPrint.setEnabled(enable) + # self.ui.buttonPreview.setEnabled(enable) + + self.buttonSave.setStyleSheet("QPushButton {color: red;}") + self.buttonSave.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as_red.png')) + + def edit_fcgcode(self, cnc_obj): + assert isinstance(cnc_obj, FlatCAMCNCJob) + self.gcode_obj = cnc_obj + + preamble = str(self.ui.prepend_text.get_value()) + postamble = str(self.ui.append_text.get_value()) + + gcode_text = self.gcode_obj.source_file + + self.gcode_editor_tab.buttonSave.clicked.connect(self.on_update_source_file) + + self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor')) + + self.ui.gcode_editor_tab.load_text(self, gcode_text, move_to_start=True, clear_text=True) + + def update_gcode(self): + my_gcode = self.ui.gcode_editor_tab.code_editor.toPlainText() + self.gcode_obj.source_file = my_gcode + + self.ui.gcode_editor_tab.buttonSave.setStyleSheet("") + self.ui.gcode_editor_tab.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png')) + + def handleOpen(self, filt=None): + self.app.defaults.report_usage("handleOpen()") + + if filt: + _filter_ = filt + else: + _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \ + "All Files (*.*)" + + path, _f = QtWidgets.QFileDialog.getOpenFileName( + caption=_('Open file'), directory=self.app.get_last_folder(), filter=_filter_) + + if path: + file = QtCore.QFile(path) + if file.open(QtCore.QIODevice.ReadOnly): + stream = QtCore.QTextStream(file) + self.code_edited = stream.readAll() + self.ui.gcode_editor_tab.load_text(self, self.code_edited, move_to_start=True, clear_text=True) + file.close() + + +class appGCodeEditorUI: + def __init__(self, app): + self.app = app + + # Number of decimals used by tools in this class + self.decimals = self.app.decimals + + # ## Current application units in Upper Case + self.units = self.app.defaults['units'].upper() + + # self.setSizePolicy( + # QtWidgets.QSizePolicy.MinimumExpanding, + # QtWidgets.QSizePolicy.MinimumExpanding + # ) + + self.layout = QtWidgets.QVBoxLayout() + self.layout.setContentsMargins(0, 0, 0, 0) + + self.editor_frame = QtWidgets.QFrame() + self.editor_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.editor_frame) + + self.editor_layout = QtWidgets.QGridLayout(self.editor_frame) + self.editor_layout.setContentsMargins(2, 2, 2, 2) + self.editor_frame.setLayout(self.editor_layout) + + # ############################################################################################################# + # ############# ADD a new TAB in the PLot Tab Area + # ############################################################################################################# + self.gcode_editor_tab = AppTextEditor(app=self.app, plain_text=True) + + # add the tab if it was closed + self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Code Editor")) + self.gcode_editor_tab.setObjectName('code_editor_tab') + + # delete the absolute and relative position and messages in the infobar + self.app.ui.position_label.setText("") + self.app.ui.rel_position_label.setText("") + + self.gcode_editor_tab.code_editor.completer_enable = False + self.gcode_editor_tab.buttonRun.hide() + + # Switch plot_area to CNCJob tab + self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab) + + self.gcode_editor_tab.t_frame.hide() + # then append the text from GCode to the text editor + try: + self.gcode_editor_tab.load_text(self.app.gcode_edited.getvalue(), move_to_start=True, clear_text=True) + except Exception as e: + log.debug('FlatCAMCNNJob.on_edit_code_click() -->%s' % str(e)) + self.app.inform.emit('[ERROR] %s %s' % ('FlatCAMCNNJob.on_edit_code_click() -->', str(e))) + return + + self.gcode_editor_tab.t_frame.show() + self.app.proc_container.view.set_idle() + + self.layout.addStretch() + + # Editor + self.exit_editor_button = QtWidgets.QPushButton(_('Exit Editor')) + self.exit_editor_button.setIcon(QtGui.QIcon(self.app.resource_location + '/power16.png')) + self.exit_editor_button.setToolTip( + _("Exit from Editor.") + ) + self.exit_editor_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.layout.addWidget(self.exit_editor_button) + # ############################ FINSIHED GUI ################################### + # ############################################################################# + + def confirmation_message(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"), + self.decimals, + minval, + self.decimals, + maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) + + def confirmation_message_int(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' % + (_("Edited value is out of range"), minval, maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) diff --git a/appGUI/GUIElements.py b/appGUI/GUIElements.py index 3039d10b..6cbf10c6 100644 --- a/appGUI/GUIElements.py +++ b/appGUI/GUIElements.py @@ -1557,8 +1557,13 @@ class FCInputDialog(QtWidgets.QInputDialog): class FCButton(QtWidgets.QPushButton): - def __init__(self, parent=None): - super(FCButton, self).__init__(parent) + def __init__(self, text=None, checkable=None, click_callback=None, parent=None): + super(FCButton, self).__init__(text, parent) + if not checkable is None: + self.setCheckable(checkable) + + if not click_callback is None: + self.clicked.connect(click_callback) def get_value(self): return self.isChecked() diff --git a/appGUI/ObjectUI.py b/appGUI/ObjectUI.py index 7ea12e96..da1859d1 100644 --- a/appGUI/ObjectUI.py +++ b/appGUI/ObjectUI.py @@ -1870,7 +1870,7 @@ class CNCObjectUI(ObjectUI): self.custom_box.addWidget(self.updateplot_button) # Editor - self.editor_button = QtWidgets.QPushButton(_('GCode Editor')) + self.editor_button = FCButton(_('GCode Editor')) self.editor_button.setIcon(QtGui.QIcon(self.app.resource_location + '/edit_file32.png')) self.editor_button.setToolTip( diff --git a/appObjects/FlatCAMCNCJob.py b/appObjects/FlatCAMCNCJob.py index c434b056..a6e55750 100644 --- a/appObjects/FlatCAMCNCJob.py +++ b/appObjects/FlatCAMCNCJob.py @@ -149,6 +149,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.gcode_editor_tab = None + self.source_file = '' self.units_found = self.app.defaults['units'] def build_ui(self): @@ -538,10 +539,14 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.ui.name_entry.set_value(new_name) self.on_name_activate(silent=True) - preamble = str(self.ui.prepend_text.get_value()) - postamble = str(self.ui.append_text.get_value()) + try: + preamble = str(self.ui.prepend_text.get_value()) + postamble = str(self.ui.append_text.get_value()) + gc = self.export_gcode(filename, preamble=preamble, postamble=postamble) + except Exception as err: + log.debug("CNCJobObject.export_gcode_handler() --> %s" % str(err)) + gc = self.export_gcode(filename) - gc = self.export_gcode(filename, preamble=preamble, postamble=postamble) if gc == 'fail': return @@ -597,8 +602,13 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.gcode_editor_tab.t_frame.show() self.app.proc_container.view.set_idle() + self.gcode_editor_tab.buttonSave.clicked.connect(self.on_update_source_file) + self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor')) + def on_update_source_file(self): + self.source_file = self.gcode_editor_tab.code_editor.toPlainText() + def gcode_header(self, comment_start_symbol=None, comment_stop_symbol=None): """ Will create a header to be added to all GCode files generated by FlatCAM @@ -735,6 +745,11 @@ class CNCJobObject(FlatCAMObj, CNCjob): include_header = True + if preamble == '': + preamble = self.app.defaults["cncjob_prepend"] + if postamble == '': + preamble = self.app.defaults["cncjob_append"] + try: if self.special_group: self.app.inform.emit('[WARNING_NOTCL] %s %s %s.' % diff --git a/app_Main.py b/app_Main.py index e69dadd4..28e9f81b 100644 --- a/app_Main.py +++ b/app_Main.py @@ -7745,10 +7745,7 @@ class App(QtCore.QObject): # then append the text from GCode to the text editor if obj.kind == 'cncjob': try: - file = obj.export_gcode( - preamble=self.defaults["cncjob_prepend"], - postamble=self.defaults["cncjob_append"], - to_file=True) + file = obj.export_gcode(to_file=True) if file == 'fail': return 'fail' except AttributeError: @@ -8665,14 +8662,14 @@ class App(QtCore.QObject): def job_thread_grb(app_obj): ret = make_gerber() if ret == 'fail': - self.inform.emit('[ERROR_NOTCL] %s' % _('Could not export Gerber file.')) + self.inform.emit('[ERROR_NOTCL] %s' % _('Could not export file.')) return self.worker_task.emit({'fcn': job_thread_grb, 'params': [self]}) else: gret = make_gerber() if gret == 'fail': - self.inform.emit('[ERROR_NOTCL] %s' % _('Could not export Gerber file.')) + self.inform.emit('[ERROR_NOTCL] %s' % _('Could not export file.')) return 'fail' if local_use is not None: return gret