From 9e8ab610b44eba52400b39358f641ca1b93ff8de Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 22 Jul 2020 15:49:52 +0300 Subject: [PATCH 1/4] - 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 From cf78211a6f779ae25309c969ca9593a98543a33f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 22 Jul 2020 17:08:40 +0300 Subject: [PATCH 2/4] - wip in the GCode Editor --- CHANGELOG.md | 1 + appEditors/AppExcEditor.py | 2 +- appEditors/appGCodeEditor.py | 194 ++++++++++++++++++--------- appObjects/FlatCAMCNCJob.py | 3 +- appObjects/FlatCAMGeometry.py | 30 +++-- appTools/ToolDrilling.py | 1 + appTools/ToolSolderPaste.py | 4 + app_Main.py | 12 +- tclCommands/TclCommandDrillcncjob.py | 1 + 9 files changed, 174 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f57c81e8..eb38c754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG for FlatCAM beta 22.07.2020 - working on a proper GCode Editor +- wip in the GCode Editor 21.07.2020 diff --git a/appEditors/AppExcEditor.py b/appEditors/AppExcEditor.py index 7640d406..77d1479f 100644 --- a/appEditors/AppExcEditor.py +++ b/appEditors/AppExcEditor.py @@ -2569,7 +2569,7 @@ class AppExcEditor(QtCore.QObject): self.set_ui() - # now that we hava data, create the appGUI interface and add it to the Tool Tab + # now that we have data, create the appGUI interface and add it to the Tool Tab self.build_ui(first_run=True) # we activate this after the initial build as we don't need to see the tool been populated diff --git a/appEditors/appGCodeEditor.py b/appEditors/appGCodeEditor.py index 61a4e0f2..af186ca0 100644 --- a/appEditors/appGCodeEditor.py +++ b/appEditors/appGCodeEditor.py @@ -6,8 +6,8 @@ # ########################################################## from appEditors.AppTextEditor import AppTextEditor -from appObjects import FlatCAMCNCJob -from appGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber, FCButton +from appObjects.FlatCAMCNCJob import CNCJobObject +from appGUI.GUIElements import FCTextArea, FCEntry, FCButton from PyQt5 import QtWidgets, QtCore, QtGui # from io import StringIO @@ -25,7 +25,7 @@ if '_' not in builtins.__dict__: log = logging.getLogger('base') -class appGCodeEditor(QtCore.QObject): +class AppGCodeEditor(QtCore.QObject): def __init__(self, app, parent=None): super().__init__(parent=parent) @@ -34,7 +34,7 @@ class appGCodeEditor(QtCore.QObject): self.plain_text = '' self.callback = lambda x: None - self.ui = appGCodeEditorUI(app=self.app) + self.ui = AppGCodeEditorUI(app=self.app) # ################################################################################# # ################### SIGNALS ##################################################### @@ -44,10 +44,44 @@ class appGCodeEditor(QtCore.QObject): self.code_edited = '' def set_ui(self): - pass + # ############################################################################################################# + # ############# ADD a new TAB in the PLot Tab Area + # ############################################################################################################# + self.ui.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.ui.gcode_editor_tab, '%s' % _("Code Editor")) + self.ui.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.ui.gcode_editor_tab.code_editor.completer_enable = False + self.ui.gcode_editor_tab.buttonRun.hide() + + # Switch plot_area to CNCJob tab + self.app.ui.plot_tab_area.setCurrentWidget(self.ui.gcode_editor_tab) + + self.ui.gcode_editor_tab.t_frame.hide() + + self.ui.gcode_editor_tab.t_frame.show() + self.app.proc_container.view.set_idle() + # ############################################################################################################# + # ############################################################################################################# + + self.ui.append_text.set_value(self.app.defaults["cncjob_append"]) + self.ui.prepend_text.set_value(self.app.defaults["cncjob_prepend"]) + + self.ui.exit_editor_button.buttonSave.clicked.connect(self.update_fcgcode) def build_ui(self): - pass + # Remove anything else in the GUI Selected Tab + self.app.ui.selected_scroll_area.takeWidget() + # Put ourselves in the GUI Selected Tab + self.app.ui.selected_scroll_area.setWidget(self.ui.edit_widget) + # Switch notebook to Selected page + self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) def ui_connect(self): pass @@ -64,35 +98,31 @@ class appGCodeEditor(QtCore.QObject): self.buttonSave.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as_red.png')) def edit_fcgcode(self, cnc_obj): - assert isinstance(cnc_obj, FlatCAMCNCJob) + assert isinstance(cnc_obj, CNCJobObject) 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.set_ui() + self.build_ui() + # then append the text from GCode to the text editor + self.ui.gcode_editor_tab.load_text(gcode_text, move_to_start=True, clear_text=True) 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): + def update_fcgcode(self): + preamble = str(self.ui.prepend_text.get_value()) + postamble = str(self.ui.append_text.get_value()) 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()") + def on_open_gcode(self): - if filt: - _filter_ = filt - else: - _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \ - "All Files (*.*)" + _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_) @@ -102,11 +132,11 @@ class appGCodeEditor(QtCore.QObject): 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) + self.ui.gcode_editor_tab.load_text(self.code_edited, move_to_start=True, clear_text=True) file.close() -class appGCodeEditorUI: +class AppGCodeEditorUI: def __init__(self, app): self.app = app @@ -121,52 +151,96 @@ class appGCodeEditorUI: # QtWidgets.QSizePolicy.MinimumExpanding # ) - self.layout = QtWidgets.QVBoxLayout() - self.layout.setContentsMargins(0, 0, 0, 0) + self.gcode_editor_tab = None - self.editor_frame = QtWidgets.QFrame() - self.editor_frame.setContentsMargins(0, 0, 0, 0) - self.layout.addWidget(self.editor_frame) + self.edit_widget = QtWidgets.QWidget() + # ## Box for custom widgets + # This gets populated in offspring implementations. + layout = QtWidgets.QVBoxLayout() + self.edit_widget.setLayout(layout) - 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 frame and inside add a vertical box layout. Inside this vbox layout I add all the Drills widgets + # this way I can hide/show the frame + self.edit_frame = QtWidgets.QFrame() + self.edit_frame.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.edit_frame) + self.edit_box = QtWidgets.QVBoxLayout() + self.edit_box.setContentsMargins(0, 0, 0, 0) + self.edit_frame.setLayout(self.edit_box) - # ############################################################################################################# - # ############# ADD a new TAB in the PLot Tab Area - # ############################################################################################################# - self.gcode_editor_tab = AppTextEditor(app=self.app, plain_text=True) + # ## Page Title box (spacing between children) + self.title_box = QtWidgets.QHBoxLayout() + self.edit_box.addLayout(self.title_box) - # 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') + # ## Page Title icon + pixmap = QtGui.QPixmap(self.app.resource_location + '/flatcam_icon32.png') + self.icon = QtWidgets.QLabel() + self.icon.setPixmap(pixmap) + self.title_box.addWidget(self.icon, stretch=0) - # delete the absolute and relative position and messages in the infobar - self.app.ui.position_label.setText("") - self.app.ui.rel_position_label.setText("") + # ## Title label + self.title_label = QtWidgets.QLabel("%s" % _('GCode Editor')) + self.title_label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) + self.title_box.addWidget(self.title_label, stretch=1) - self.gcode_editor_tab.code_editor.completer_enable = False - self.gcode_editor_tab.buttonRun.hide() + # ## Object name + self.name_box = QtWidgets.QHBoxLayout() + self.edit_box.addLayout(self.name_box) + name_label = QtWidgets.QLabel(_("Name:")) + self.name_box.addWidget(name_label) + self.name_entry = FCEntry() + self.name_box.addWidget(self.name_entry) - # Switch plot_area to CNCJob tab - self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab) + # Prepend text to GCode + prependlabel = QtWidgets.QLabel('%s:' % _('Prepend to CNC Code')) + prependlabel.setToolTip( + _("Type here any G-Code commands you would\n" + "like to add at the beginning of the G-Code file.") + ) + self.edit_box.addWidget(prependlabel) - 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.prepend_text = FCTextArea() + self.prepend_text.setPlaceholderText( + _("Type here any G-Code commands you would\n" + "like to add at the beginning of the G-Code file.") + ) + self.edit_box.addWidget(self.prepend_text) - self.gcode_editor_tab.t_frame.show() - self.app.proc_container.view.set_idle() + # Append text to GCode + appendlabel = QtWidgets.QLabel('%s:' % _('Append to CNC Code')) + appendlabel.setToolTip( + _("Type here any G-Code commands you would\n" + "like to append to the generated file.\n" + "I.e.: M2 (End of program)") + ) + self.edit_box.addWidget(appendlabel) - self.layout.addStretch() + self.append_text = FCTextArea() + self.append_text.setPlaceholderText( + _("Type here any G-Code commands you would\n" + "like to append to the generated file.\n" + "I.e.: M2 (End of program)") + ) + self.edit_box.addWidget(self.append_text) + + h_lay = QtWidgets.QHBoxLayout() + h_lay.setAlignment(QtCore.Qt.AlignVCenter) + self.edit_box.addLayout(h_lay) + + # GO Button + self.update_gcode_button = FCButton(_('Update Code')) + # self.update_gcode_button.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png')) + self.update_gcode_button.setToolTip( + _("Update the Gcode in the Editor with the values\n" + "in the 'Prepend' and 'Append' text boxes.") + ) + + h_lay.addWidget(self.update_gcode_button) + + layout.addStretch() # Editor - self.exit_editor_button = QtWidgets.QPushButton(_('Exit Editor')) + self.exit_editor_button = FCButton(_('Exit Editor')) self.exit_editor_button.setIcon(QtGui.QIcon(self.app.resource_location + '/power16.png')) self.exit_editor_button.setToolTip( _("Exit from Editor.") @@ -177,9 +251,9 @@ class appGCodeEditorUI: font-weight: bold; } """) - self.layout.addWidget(self.exit_editor_button) - # ############################ FINSIHED GUI ################################### - # ############################################################################# + layout.addWidget(self.exit_editor_button) + # ############################ FINSIHED GUI ################################################################## + # ############################################################################################################# def confirmation_message(self, accepted, minval, maxval): if accepted is False: diff --git a/appObjects/FlatCAMCNCJob.py b/appObjects/FlatCAMCNCJob.py index a6e55750..9fd26b27 100644 --- a/appObjects/FlatCAMCNCJob.py +++ b/appObjects/FlatCAMCNCJob.py @@ -595,8 +595,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): 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))) + log.debug('FlatCAMCNCJob.on_edit_code_click() -->%s' % str(e)) return self.gcode_editor_tab.t_frame.show() diff --git a/appObjects/FlatCAMGeometry.py b/appObjects/FlatCAMGeometry.py index 76395ebd..7c85b45b 100644 --- a/appObjects/FlatCAMGeometry.py +++ b/appObjects/FlatCAMGeometry.py @@ -1881,6 +1881,7 @@ class GeometryObject(FlatCAMObj, Geometry): job_obj.z_pdepth = float(self.app.defaults["geometry_z_pdepth"]) job_obj.feedrate_probe = float(self.app.defaults["geometry_feedrate_probe"]) + total_gcode = '' for tooluid_key in list(tools_dict.keys()): tool_cnt += 1 @@ -1970,6 +1971,8 @@ class GeometryObject(FlatCAMObj, Geometry): else: dia_cnc_dict['gcode'] = res + total_gcode += res + # tell gcode_parse from which point to start drawing the lines depending on what kind of # object is the source of gcode job_obj.toolchange_xy_type = "geometry" @@ -1993,6 +1996,8 @@ class GeometryObject(FlatCAMObj, Geometry): }) dia_cnc_dict.clear() + job_obj.source_file = total_gcode + # Object initialization function for app.app_obj.new_object() # RUNNING ON SEPARATE THREAD! def job_init_multi_geometry(job_obj, app_obj): @@ -2031,6 +2036,7 @@ class GeometryObject(FlatCAMObj, Geometry): self.app.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry')) return 'fail' + total_gcode = '' for tooluid_key in list(tools_dict.keys()): tool_cnt += 1 dia_cnc_dict = deepcopy(tools_dict[tooluid_key]) @@ -2123,6 +2129,7 @@ class GeometryObject(FlatCAMObj, Geometry): return 'fail' else: dia_cnc_dict['gcode'] = res + total_gcode += res self.app.inform.emit('[success] %s' % _("G-Code parsing in progress...")) dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse() @@ -2149,6 +2156,8 @@ class GeometryObject(FlatCAMObj, Geometry): }) dia_cnc_dict.clear() + job_obj.source_file = total_gcode + if use_thread: # To be run in separate thread def job_thread(a_obj): @@ -2288,17 +2297,18 @@ class GeometryObject(FlatCAMObj, Geometry): # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially # to a value of 0.0005 which is 20 times less than 0.01 tol = float(self.app.defaults['global_tolerance']) / 20 - job_obj.generate_from_geometry_2( - self, tooldia=tooldia, offset=offset, tolerance=tol, - z_cut=z_cut, z_move=z_move, - feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, - spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime, - multidepth=multidepth, depthpercut=depthperpass, - toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, - extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy, - pp_geometry_name=ppname_g + res = job_obj.generate_from_geometry_2(self, tooldia=tooldia, offset=offset, tolerance=tol, + z_cut=z_cut, z_move=z_move, feedrate=feedrate, + feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, + spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime, + multidepth=multidepth, depthpercut=depthperpass, + toolchange=toolchange, toolchangez=toolchangez, + toolchangexy=toolchangexy, + extracut=extracut, extracut_length=extracut_length, + startz=startz, endz=endz, endxy=endxy, + pp_geometry_name=ppname_g ) - + job_obj.source_file = res # tell gcode_parse from which point to start drawing the lines depending on what kind of object is the # source of gcode job_obj.toolchange_xy_type = "geometry" diff --git a/appTools/ToolDrilling.py b/appTools/ToolDrilling.py index 142570d6..b62cca2c 100644 --- a/appTools/ToolDrilling.py +++ b/appTools/ToolDrilling.py @@ -1797,6 +1797,7 @@ class ToolDrilling(AppTool, Excellon): self.total_gcode_parsed += tool_gcode_parsed job_obj.gcode = self.total_gcode + job_obj.source_file = self.total_gcode job_obj.gcode_parsed = self.total_gcode_parsed if job_obj.gcode == 'fail': return 'fail' diff --git a/appTools/ToolSolderPaste.py b/appTools/ToolSolderPaste.py index e0565ba7..59e567be 100644 --- a/appTools/ToolSolderPaste.py +++ b/appTools/ToolSolderPaste.py @@ -913,6 +913,7 @@ class SolderPaste(AppTool): job_obj.options['xmax'] = xmax job_obj.options['ymax'] = ymax + total_gcode = '' for tooluid_key, tooluid_value in obj.tools.items(): # find the tool_dia associated with the tooluid_key tool_dia = tooluid_value['tooldia'] @@ -934,6 +935,7 @@ class SolderPaste(AppTool): return 'fail' else: tool_cnc_dict['gcode'] = res + total_gcode += res # ## PARSE GCODE # ## tool_cnc_dict['gcode_parsed'] = job_obj.gcode_parse() @@ -949,6 +951,8 @@ class SolderPaste(AppTool): }) tool_cnc_dict.clear() + job_obj.source_file = total_gcode + if use_thread: # To be run in separate thread def job_thread(app_obj): diff --git a/app_Main.py b/app_Main.py index 28e9f81b..ebc62447 100644 --- a/app_Main.py +++ b/app_Main.py @@ -83,6 +83,7 @@ from appEditors.AppGeoEditor import AppGeoEditor from appEditors.AppExcEditor import AppExcEditor from appEditors.AppGerberEditor import AppGerberEditor from appEditors.AppTextEditor import AppTextEditor +from appEditors.appGCodeEditor import AppGCodeEditor from appParsers.ParseHPGL2 import HPGL2 # FlatCAM Workers @@ -1574,6 +1575,12 @@ class App(QtCore.QObject): self.grb_editor = AppGerberEditor(self) except Exception as es: log.debug("app_Main.__init__() --> Gerber Editor Error: %s" % str(es)) + + try: + self.gcode_editor = AppGCodeEditor(self) + except Exception as es: + log.debug("app_Main.__init__() --> GCode Editor Error: %s" % str(es)) + self.log.debug("Finished adding FlatCAM Editor's.") self.set_ui_title(name=_("New Project - Not saved")) @@ -2226,7 +2233,10 @@ class App(QtCore.QObject): if self.ui.splitter.sizes()[0] == 0: self.ui.splitter.setSizes([1, 1]) - edited_object.on_edit_code_click() + # set call source to the Editor we go into + self.call_source = 'gcode_editor' + + self.gcode_editor.edit_fcgcode(edited_object) return # make sure that we can't select another object while in Editor Mode: diff --git a/tclCommands/TclCommandDrillcncjob.py b/tclCommands/TclCommandDrillcncjob.py index 8940a697..e6a07e0a 100644 --- a/tclCommands/TclCommandDrillcncjob.py +++ b/tclCommands/TclCommandDrillcncjob.py @@ -332,6 +332,7 @@ class TclCommandDrillcncjob(TclCommandSignaled): job_obj.excellon_optimization_type = opt_type ret_val = job_obj.generate_from_excellon_by_tool(obj, tools, use_ui=False) + job_obj.source_file = ret_val if ret_val == 'fail': return 'fail' From b8fb64a1430f5cffd768a43c73c4103992c4fc56 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 23 Jul 2020 00:44:33 +0300 Subject: [PATCH 3/4] - added a Laser preprocessor named 'Z_laser' which will change the Z to the Travel Z on each ToolChange event allowing therefore control of the dot size - by default now a new blank Geometry object created by FlatCAM is of type multigeo - made sure that optimizations of lines when importing SVG or DXF as lines will not encounter polygons but only LinesStrings or LinearRings, otherwise having crashes - fixed the import SVG and import DXF, when importing as Geometry to be imported as multigeo tool - fixed the import SVG and import DXF, the source files will be saved as loaded into the source_file attribute of the resulting object (be it Geometry or Gerber) --- CHANGELOG.md | 5 ++ appEditors/AppGeoEditor.py | 142 +++++++++++++++++------------------ appEditors/appGCodeEditor.py | 59 +++++++++++++-- appObjects/AppObject.py | 30 +++++++- app_Main.py | 19 ++++- camlib.py | 26 ++++++- preprocessors/Z_laser.py | 111 +++++++++++++++++++++++++++ 7 files changed, 308 insertions(+), 84 deletions(-) create mode 100644 preprocessors/Z_laser.py diff --git a/CHANGELOG.md b/CHANGELOG.md index eb38c754..7864d6b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ CHANGELOG for FlatCAM beta - working on a proper GCode Editor - wip in the GCode Editor +- added a Laser preprocessor named 'Z_laser' which will change the Z to the Travel Z on each ToolChange event allowing therefore control of the dot size +- by default now a new blank Geometry object created by FlatCAM is of type multigeo +- made sure that optimizations of lines when importing SVG or DXF as lines will not encounter polygons but only LinesStrings or LinearRings, otherwise having crashes +- fixed the import SVG and import DXF, when importing as Geometry to be imported as multigeo tool +- fixed the import SVG and import DXF, the source files will be saved as loaded into the source_file attribute of the resulting object (be it Geometry or Gerber) 21.07.2020 diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index 5a0160ef..1a20d580 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -3979,77 +3979,6 @@ class AppGeoEditor(QtCore.QObject): # self.storage = AppGeoEditor.make_storage() self.replot() - def edit_fcgeometry(self, fcgeometry, multigeo_tool=None): - """ - Imports the geometry from the given FlatCAM Geometry object - into the editor. - - :param fcgeometry: GeometryObject - :param multigeo_tool: A tool for the case of the edited geometry being of type 'multigeo' - :return: None - """ - assert isinstance(fcgeometry, Geometry), "Expected a Geometry, got %s" % type(fcgeometry) - - self.deactivate() - self.activate() - - self.set_ui() - - # Hide original geometry - self.fcgeometry = fcgeometry - fcgeometry.visible = False - - # Set selection tolerance - DrawToolShape.tolerance = fcgeometry.drawing_tolerance * 10 - - self.select_tool("select") - - if self.app.defaults['geometry_spindledir'] == 'CW': - if self.app.defaults['geometry_editor_milling_type'] == 'cl': - milling_type = 1 # CCW motion = climb milling (spindle is rotating CW) - else: - milling_type = -1 # CW motion = conventional milling (spindle is rotating CW) - else: - if self.app.defaults['geometry_editor_milling_type'] == 'cl': - milling_type = -1 # CCW motion = climb milling (spindle is rotating CCW) - else: - milling_type = 1 # CW motion = conventional milling (spindle is rotating CCW) - - # Link shapes into editor. - if multigeo_tool: - self.multigeo_tool = multigeo_tool - geo_to_edit = self.flatten(geometry=fcgeometry.tools[self.multigeo_tool]['solid_geometry'], - orient_val=milling_type) - self.app.inform.emit( - '[WARNING_NOTCL] %s: %s %s: %s' % ( - _("Editing MultiGeo Geometry, tool"), - str(self.multigeo_tool), - _("with diameter"), - str(fcgeometry.tools[self.multigeo_tool]['tooldia']) - ) - ) - else: - geo_to_edit = self.flatten(geometry=fcgeometry.solid_geometry, orient_val=milling_type) - - for shape in geo_to_edit: - if shape is not None: - if type(shape) == Polygon: - self.add_shape(DrawToolShape(shape.exterior)) - for inter in shape.interiors: - self.add_shape(DrawToolShape(inter)) - else: - self.add_shape(DrawToolShape(shape)) - - self.replot() - - # updated units - self.units = self.app.defaults['units'].upper() - self.decimals = self.app.decimals - - # start with GRID toolbar activated - if self.app.ui.grid_snap_btn.isChecked() is False: - self.app.ui.grid_snap_btn.trigger() - def on_buffer_tool(self): buff_tool = BufferSelectionTool(self.app, self) buff_tool.run() @@ -4700,6 +4629,77 @@ class AppGeoEditor(QtCore.QObject): return snap_x, snap_y + def edit_fcgeometry(self, fcgeometry, multigeo_tool=None): + """ + Imports the geometry from the given FlatCAM Geometry object + into the editor. + + :param fcgeometry: GeometryObject + :param multigeo_tool: A tool for the case of the edited geometry being of type 'multigeo' + :return: None + """ + assert isinstance(fcgeometry, Geometry), "Expected a Geometry, got %s" % type(fcgeometry) + + self.deactivate() + self.activate() + + self.set_ui() + + # Hide original geometry + self.fcgeometry = fcgeometry + fcgeometry.visible = False + + # Set selection tolerance + DrawToolShape.tolerance = fcgeometry.drawing_tolerance * 10 + + self.select_tool("select") + + if self.app.defaults['geometry_spindledir'] == 'CW': + if self.app.defaults['geometry_editor_milling_type'] == 'cl': + milling_type = 1 # CCW motion = climb milling (spindle is rotating CW) + else: + milling_type = -1 # CW motion = conventional milling (spindle is rotating CW) + else: + if self.app.defaults['geometry_editor_milling_type'] == 'cl': + milling_type = -1 # CCW motion = climb milling (spindle is rotating CCW) + else: + milling_type = 1 # CW motion = conventional milling (spindle is rotating CCW) + + # Link shapes into editor. + if multigeo_tool: + self.multigeo_tool = multigeo_tool + geo_to_edit = self.flatten(geometry=fcgeometry.tools[self.multigeo_tool]['solid_geometry'], + orient_val=milling_type) + self.app.inform.emit( + '[WARNING_NOTCL] %s: %s %s: %s' % ( + _("Editing MultiGeo Geometry, tool"), + str(self.multigeo_tool), + _("with diameter"), + str(fcgeometry.tools[self.multigeo_tool]['tooldia']) + ) + ) + else: + geo_to_edit = self.flatten(geometry=fcgeometry.solid_geometry, orient_val=milling_type) + + for shape in geo_to_edit: + if shape is not None: + if type(shape) == Polygon: + self.add_shape(DrawToolShape(shape.exterior)) + for inter in shape.interiors: + self.add_shape(DrawToolShape(inter)) + else: + self.add_shape(DrawToolShape(shape)) + + self.replot() + + # updated units + self.units = self.app.defaults['units'].upper() + self.decimals = self.app.decimals + + # start with GRID toolbar activated + if self.app.ui.grid_snap_btn.isChecked() is False: + self.app.ui.grid_snap_btn.trigger() + def update_fcgeometry(self, fcgeometry): """ Transfers the geometry tool shape buffer to the selected geometry diff --git a/appEditors/appGCodeEditor.py b/appEditors/appGCodeEditor.py index af186ca0..51a8893e 100644 --- a/appEditors/appGCodeEditor.py +++ b/appEditors/appGCodeEditor.py @@ -36,14 +36,15 @@ class AppGCodeEditor(QtCore.QObject): self.ui = AppGCodeEditorUI(app=self.app) - # ################################################################################# - # ################### SIGNALS ##################################################### - # ################################################################################# - self.gcode_obj = None self.code_edited = '' def set_ui(self): + """ + + :return: + :rtype: + """ # ############################################################################################################# # ############# ADD a new TAB in the PLot Tab Area # ############################################################################################################# @@ -73,9 +74,18 @@ class AppGCodeEditor(QtCore.QObject): self.ui.append_text.set_value(self.app.defaults["cncjob_append"]) self.ui.prepend_text.set_value(self.app.defaults["cncjob_prepend"]) - self.ui.exit_editor_button.buttonSave.clicked.connect(self.update_fcgcode) + # ################################################################################# + # ################### SIGNALS ##################################################### + # ################################################################################# + self.ui.update_gcode_button.clicked.connect(self.insert_gcode) + self.ui.exit_editor_button.clicked.connect(self.update_fcgcode) def build_ui(self): + """ + + :return: + :rtype: + """ # Remove anything else in the GUI Selected Tab self.app.ui.selected_scroll_area.takeWidget() # Put ourselves in the GUI Selected Tab @@ -84,12 +94,27 @@ class AppGCodeEditor(QtCore.QObject): self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) def ui_connect(self): + """ + + :return: + :rtype: + """ pass def ui_disconnect(self): + """ + + :return: + :rtype: + """ pass def handleTextChanged(self): + """ + + :return: + :rtype: + """ # enable = not self.ui.code_editor.document().isEmpty() # self.ui.buttonPrint.setEnabled(enable) # self.ui.buttonPreview.setEnabled(enable) @@ -97,7 +122,22 @@ class AppGCodeEditor(QtCore.QObject): self.buttonSave.setStyleSheet("QPushButton {color: red;}") self.buttonSave.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as_red.png')) + def insert_gcode(self): + """ + + :return: + :rtype: + """ + pass + def edit_fcgcode(self, cnc_obj): + """ + + :param cnc_obj: + :type cnc_obj: + :return: + :rtype: + """ assert isinstance(cnc_obj, CNCJobObject) self.gcode_obj = cnc_obj @@ -111,6 +151,11 @@ class AppGCodeEditor(QtCore.QObject): self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor')) def update_fcgcode(self): + """ + + :return: + :rtype: + """ preamble = str(self.ui.prepend_text.get_value()) postamble = str(self.ui.append_text.get_value()) my_gcode = self.ui.gcode_editor_tab.code_editor.toPlainText() @@ -120,7 +165,11 @@ class AppGCodeEditor(QtCore.QObject): self.ui.gcode_editor_tab.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png')) def on_open_gcode(self): + """ + :return: + :rtype: + """ _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \ "All Files (*.*)" diff --git a/appObjects/AppObject.py b/appObjects/AppObject.py index 9387384b..e485cce1 100644 --- a/appObjects/AppObject.py +++ b/appObjects/AppObject.py @@ -223,11 +223,37 @@ class AppObject(QtCore.QObject): :return: None """ + outname = 'new_geo' def initialize(obj, app): - obj.multitool = False + obj.multitool = True + obj.multigeo = True + # store here the default data for Geometry Data + default_data = {} - self.new_object('geometry', 'new_geo', initialize, plot=False) + for opt_key, opt_val in app.options.items(): + if opt_key.find('geometry' + "_") == 0: + oname = opt_key[len('geometry') + 1:] + default_data[oname] = self.app.options[opt_key] + if opt_key.find('tools_mill' + "_") == 0: + oname = opt_key[len('tools_mill') + 1:] + default_data[oname] = self.app.options[opt_key] + + obj.tools = {} + obj.tools.update({ + 1: { + 'tooldia': float(app.defaults["geometry_cnctooldia"]), + 'offset': 'Path', + 'offset_value': 0.0, + 'type': _('Rough'), + 'tool_type': 'C1', + 'data': deepcopy(default_data), + 'solid_geometry': [] + } + }) + obj.tools[1]['data']['name'] = outname + + self.new_object('geometry', outname, initialize, plot=False) def new_gerber_object(self): """ diff --git a/app_Main.py b/app_Main.py index ebc62447..45a73f77 100644 --- a/app_Main.py +++ b/app_Main.py @@ -2193,9 +2193,10 @@ class App(QtCore.QObject): if edited_object.tools[tool]['tooldia'] == selected_tooldia: multi_tool = tool break - + log.debug("Editing MultiGeo Geometry with tool diameter: %s" % str(multi_tool)) self.geo_editor.edit_fcgeometry(edited_object, multigeo_tool=multi_tool) else: + log.debug("Editing SingleGeo Geometry with tool diameter.") self.geo_editor.edit_fcgeometry(edited_object) # set call source to the Editor we go into @@ -8780,9 +8781,15 @@ class App(QtCore.QObject): units = self.defaults['units'].upper() def obj_init(geo_obj, app_obj): - geo_obj.import_svg(filename, obj_type, units=units) - geo_obj.multigeo = False - geo_obj.source_file = self.export_gerber(obj_name=name, filename=None, local_use=geo_obj, use_thread=False) + if obj_type == "geometry": + geo_obj.import_svg(filename, obj_type, units=units) + elif obj_type == "gerber": + geo_obj.import_svg(filename, obj_type, units=units) + + geo_obj.multigeo = True + with open(filename) as f: + file_content = f.read() + geo_obj.source_file = file_content with self.proc_container.new(_("Importing SVG")) as proc: @@ -8833,7 +8840,11 @@ class App(QtCore.QObject): geo_obj.import_dxf_as_gerber(filename, units=units) else: return "fail" + geo_obj.multigeo = True + with open(filename) as f: + file_content = f.read() + geo_obj.source_file = file_content with self.proc_container.new(_("Importing DXF")): diff --git a/camlib.py b/camlib.py index 14cb1fb2..ecf81922 100644 --- a/camlib.py +++ b/camlib.py @@ -1058,6 +1058,7 @@ class Geometry(object): geos = [translate(scale(g, 1.0, -1.0, origin=(0, 0)), yoff=h) for g in geos] # trying to optimize the resulting geometry by merging contiguous lines + geos = self.flatten(geos, reset=True, pathonly=True) geos = linemerge(geos) # Add to object @@ -1081,12 +1082,31 @@ class Geometry(object): if flip: # Change origin to bottom left for i in geos_text: - _, minimy, _, maximy = i.bounds + __, minimy, __, maximy = i.bounds h2 = (maximy - minimy) * 0.5 geos_text_f.append(translate(scale(i, 1.0, -1.0, origin=(0, 0)), yoff=(h + h2))) if geos_text_f: self.solid_geometry = self.solid_geometry + geos_text_f + tooldia = float(self.app.defaults["geometry_cnctooldia"]) + tooldia = float('%.*f' % (self.decimals, tooldia)) + + new_data = {k: v for k, v in self.options.items()} + + self.tools.update({ + 1: { + 'tooldia': tooldia, + 'offset': 'Path', + 'offset_value': 0.0, + 'type': _('Rough'), + 'tool_type': 'C1', + 'data': deepcopy(new_data), + 'solid_geometry': self.solid_geometry + } + }) + + self.tools[1]['data']['name'] = self.options['name'] + def import_dxf_as_geo(self, filename, units='MM'): """ Imports shapes from an DXF file into the object's geometry. @@ -1103,6 +1123,7 @@ class Geometry(object): geos = getdxfgeo(dxf) # trying to optimize the resulting geometry by merging contiguous lines + geos = self.flatten(geos, reset=True, pathonly=True) geos = linemerge(geos) # Add to object @@ -5176,7 +5197,8 @@ class CNCjob(Geometry): geo_storage = {} for geo in temp_solid_geometry: - geo_storage[geo.coords[0]] = geo + if not geo is None: + geo_storage[geo.coords[0]] = geo locations = list(geo_storage.keys()) if opt_type == 'M': diff --git a/preprocessors/Z_laser.py b/preprocessors/Z_laser.py new file mode 100644 index 00000000..73e8db4d --- /dev/null +++ b/preprocessors/Z_laser.py @@ -0,0 +1,111 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# File Author: Matthieu Berthomé # +# Date: 5/26/2017 # +# MIT Licence # +# ########################################################## + +from appPreProcessor import * + +# This post processor is configured to output code that +# is compatible with almost any version of Grbl. + + +class Z_laser(PreProc): + + include_header = True + coordinate_format = "%.*f" + feedrate_format = '%.*f' + + def start_code(self, p): + units = ' ' + str(p['units']).lower() + gcode = '(This preprocessor is used with a motion controller loaded with GRBL firmware. )\n' + gcode += '(It is for the case when it is used together with a LASER connected on the SPINDLE connector.)\n' + gcode += '(On toolchange event the laser will move to a defined Z height to change the laser dot size.)\n\n' + + xmin = '%.*f' % (p.coords_decimals, p['options']['xmin']) + xmax = '%.*f' % (p.coords_decimals, p['options']['xmax']) + ymin = '%.*f' % (p.coords_decimals, p['options']['ymin']) + ymax = '%.*f' % (p.coords_decimals, p['options']['ymax']) + + gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n' + gcode += '(Feedrate rapids: ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n' + + gcode += '(Z Focus: ' + str(p['z_move']) + units + ')\n' + + gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n' + + if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': + gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + else: + gcode += '(Preprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n' + + gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n' + gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n' + + gcode += '(Laser Power (Spindle Speed): ' + str(p['spindlespeed']) + ')\n\n' + + gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n" + gcode += 'G90\n' + gcode += 'G17\n' + gcode += 'G94' + + return gcode + + def startz_code(self, p): + return '' + + def lift_code(self, p): + return 'M5' + + def down_code(self, p): + sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir] + if p.spindlespeed: + return '%s S%s' % (sdir, str(p.spindlespeed)) + else: + return sdir + + def toolchange_code(self, p): + return 'G00 Z' + self.coordinate_format % (p.coords_decimals, p.z_move) + + def up_to_zero_code(self, p): + return 'M5' + + def position_code(self, p): + return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \ + (p.coords_decimals, p.x, p.coords_decimals, p.y) + + def rapid_code(self, p): + return ('G00 ' + self.position_code(p)).format(**p) + + def linear_code(self, p): + return ('G01 ' + self.position_code(p)).format(**p) + \ + ' F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate)) + + def end_code(self, p): + coords_xy = p['xy_end'] + gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n") + + if coords_xy and coords_xy != '': + gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n" + return gcode + + def feedrate_code(self, p): + return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate)) + + def z_feedrate_code(self, p): + return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate)) + + def spindle_code(self, p): + sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir] + if p.spindlespeed: + return '%s S%s' % (sdir, str(p.spindlespeed)) + else: + return sdir + + def dwell_code(self, p): + return '' + + def spindle_stop_code(self, p): + return 'M5' From 5142b51590fdb7ab300ed08b8bc68ec76c12919f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 23 Jul 2020 01:01:40 +0300 Subject: [PATCH 4/4] - in import SVG and import DXF methods made sure that any polygons that are imported as polygons will survive and only the lines are optimized (changed the behavior of the above made modification) --- CHANGELOG.md | 1 + camlib.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7864d6b2..a09e90b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ CHANGELOG for FlatCAM beta - made sure that optimizations of lines when importing SVG or DXF as lines will not encounter polygons but only LinesStrings or LinearRings, otherwise having crashes - fixed the import SVG and import DXF, when importing as Geometry to be imported as multigeo tool - fixed the import SVG and import DXF, the source files will be saved as loaded into the source_file attribute of the resulting object (be it Geometry or Gerber) +- in import SVG and import DXF methods made sure that any polygons that are imported as polygons will survive and only the lines are optimized (changed the behavior of the above made modification) 21.07.2020 diff --git a/camlib.py b/camlib.py index ecf81922..84baeb4e 100644 --- a/camlib.py +++ b/camlib.py @@ -1058,8 +1058,19 @@ class Geometry(object): geos = [translate(scale(g, 1.0, -1.0, origin=(0, 0)), yoff=h) for g in geos] # trying to optimize the resulting geometry by merging contiguous lines - geos = self.flatten(geos, reset=True, pathonly=True) - geos = linemerge(geos) + geos = list(self.flatten_list(geos)) + geos_polys = [] + geos_lines = [] + for g in geos: + if isinstance(g, Polygon): + geos_polys.append(g) + else: + geos_lines.append(g) + + merged_lines = linemerge(geos_lines) + geos = geos_polys + for l in merged_lines: + geos.append(l) # Add to object if self.solid_geometry is None: @@ -1123,8 +1134,19 @@ class Geometry(object): geos = getdxfgeo(dxf) # trying to optimize the resulting geometry by merging contiguous lines - geos = self.flatten(geos, reset=True, pathonly=True) - geos = linemerge(geos) + geos = list(self.flatten_list(geos)) + geos_polys = [] + geos_lines = [] + for g in geos: + if isinstance(g, Polygon): + geos_polys.append(g) + else: + geos_lines.append(g) + + merged_lines = linemerge(geos_lines) + geos = geos_polys + for l in merged_lines: + geos.append(l) # Add to object if self.solid_geometry is None: