Sfoglia il codice sorgente

- changed the name of the new object FlatCAMNotes to a more general one FlatCAMDocument
- changed the way a new FlatCAMScript object is made, the method that is processing the Tcl commands when the Run button is clicked is moved to the FlatCAMObj.FlatCAMScript() class
- reused the Multiprocessing Pool declared in the App for the ToolRulesCheck() class
- adapted the Project context menu for the new types of FLatCAM objects
- modified the setup_recent_files to accommodate the new FlatCAM objects
- made sure that when an FlatCAM script object is deleted, it's associated Tab is closed

Marius Stanciu 6 anni fa
parent
commit
a75bdfb29d

+ 215 - 128
FlatCAMApp.py

@@ -29,7 +29,7 @@ import gc
 from xml.dom.minidom import parseString as parse_xml_string
 
 from multiprocessing.connection import Listener, Client
-from multiprocessing import Pool
+from multiprocessing import Pool, cpu_count
 import socket
 from array import array
 
@@ -376,7 +376,7 @@ class App(QtCore.QObject):
         # #############################################################################
         # ##################### CREATE MULTIPROCESSING POOL ###########################
         # #############################################################################
-        self.pool = Pool()
+        self.pool = Pool(processes=cpu_count())
 
         # ##########################################################################
         # ################## Setting the Splash Screen #############################
@@ -1227,9 +1227,14 @@ class App(QtCore.QObject):
                                           'minoffset, multidepth, name, offset, opt_type, order, outname, overlap, '
                                           'passes, postamble, pp, ppname_e, ppname_g, preamble, radius, ref, rest, '
                                           'rows, shellvar_, scale_factor, spacing_columns, spacing_rows, spindlespeed, '
-                                          'toolchange_xy, use_threads, value, x, x0, x1, y, y0, y1, z_cut, z_move'
-,
-
+                                          'toolchange_xy, use_threads, value, x, x0, x1, y, y0, y1, z_cut, z_move',
+            "script_autocompleter": True,
+            "script_text": "",
+            "script_plot": True,
+            "script_source_file": "",
+            "document_text": "",
+            "document_plot": True,
+            "document_source_file": "",
         })
 
         # ############################################################
@@ -1514,9 +1519,11 @@ class App(QtCore.QObject):
             "tools_panelize_constrainy": 0.0,
 
             "script_text": "",
-            "script_plot": True,
-            "notes_text": "",
-            "notes_plot": True,
+            "script_plot": False,
+            "script_source_file": "",
+            "document_text": "",
+            "document_plot": False,
+            "document_source_file": "",
 
         })
 
@@ -3955,7 +3962,7 @@ class App(QtCore.QObject):
             "cncjob": FlatCAMCNCjob,
             "geometry": FlatCAMGeometry,
             "script": FlatCAMScript,
-            "notes": FlatCAMNotes
+            "document": FlatCAMDocument
         }
 
         App.log.debug("Calling object constructor...")
@@ -4019,8 +4026,8 @@ class App(QtCore.QObject):
             self.log.debug("%f seconds converting units." % (t3 - t2))
 
         # Create the bounding box for the object and then add the results to the obj.options
-        # But not for Scripts or for Notes
-        if kind != 'notes' and kind != 'script':
+        # But not for Scripts or for Documents
+        if kind != 'document' and kind != 'script':
             try:
                 xmin, ymin, xmax, ymax = obj.bounds()
                 obj.options['xmin'] = xmin
@@ -4031,6 +4038,9 @@ class App(QtCore.QObject):
                 log.warning("The object has no bounds properties. %s" % str(e))
                 return "fail"
 
+        # update the KeyWords list with the name of the file
+        self.myKeywords.append(obj.options['name'])
+
         FlatCAMApp.App.log.debug("Moving new object back to main thread.")
 
         # Move the object to the main thread and let the app know that it is available.
@@ -4089,7 +4099,7 @@ class App(QtCore.QObject):
 
         self.new_object('gerber', 'new_grb', initialize, plot=False)
 
-    def new_script_object(self, name=None):
+    def new_script_object(self, name=None, text=None):
         """
         Creates a new, blank TCL Script object.
         :param name: a name for the new object
@@ -4097,21 +4107,30 @@ class App(QtCore.QObject):
         """
         self.report_usage("new_script_object()")
 
-        def initialize(obj, self):
-            obj.source_file = _(
-            "#\n"
-            "# CREATE A NEW FLATCAM TCL SCRIPT\n"
-            "# TCL Tutorial here: https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n"
-            "#\n\n"
-            "# FlatCAM commands list:\n"
-            "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, AlignDrillGrid, ClearShell, ClearCopper,\n"
-            "# Cncjob, Cutout, Delete, Drillcncjob, ExportGcode, ExportSVG, Exteriors, GeoCutout, GeoUnion, GetNames,\n"
-            "# GetSys, ImportSvg, Interiors, Isolate, Follow, JoinExcellon, JoinGeometry, ListSys, MillDrills,\n"
-            "# MillSlots, Mirror, New, NewGeometry, Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n"
-            "# Options, Paint, Panelize, Plot, SaveProject, SaveSys, Scale, SetActive, SetSys, Skew, SubtractPoly,\n"
-            "# SubtractRectangle, Version, WriteGCode\n"
-            "#\n\n"
+        if text is not None:
+            new_source_file = text
+        else:
+            new_source_file = _(
+                "#\n"
+                "# CREATE A NEW FLATCAM TCL SCRIPT\n"
+                "# TCL Tutorial here: https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n"
+                "#\n\n"
+                "# FlatCAM commands list:\n"
+                "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, AlignDrillGrid, ClearShell, "
+                "ClearCopper,\n"
+                "# Cncjob, Cutout, Delete, Drillcncjob, ExportGcode, ExportSVG, Exteriors, GeoCutout, GeoUnion, "
+                "GetNames,\n"
+                "# GetSys, ImportSvg, Interiors, Isolate, Follow, JoinExcellon, JoinGeometry, ListSys, MillDrills,\n"
+                "# MillSlots, Mirror, New, NewGeometry, Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n"
+                "# Options, Paint, Panelize, Plot, SaveProject, SaveSys, Scale, SetActive, SetSys, Skew, "
+                "SubtractPoly,\n"
+                "# SubtractRectangle, Version, WriteGCode\n"
+                "#\n\n"
         )
+
+        def initialize(obj, self):
+            obj.source_file = deepcopy(new_source_file)
+
         if name is None:
             outname = 'new_script'
         else:
@@ -4119,19 +4138,18 @@ class App(QtCore.QObject):
 
         self.new_object('script', outname, initialize, plot=False)
 
-
-    def new_notes_object(self):
+    def new_document_object(self):
         """
-        Creates a new, blank Notes object.
+        Creates a new, blank Document object.
 
         :return: None
         """
-        self.report_usage("new_notes_object()")
+        self.report_usage("new_document_object()")
 
         def initialize(obj, self):
             obj.source_file = ""
 
-        self.new_object('notes', 'new_notes', initialize, plot=False)
+        self.new_object('document', 'new_document', initialize, plot=False)
 
     def on_object_created(self, obj, plot, autoselect):
         """
@@ -4169,12 +4187,11 @@ class App(QtCore.QObject):
         elif obj.kind == 'script':
             self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
                 kind=obj.kind.capitalize(), color='orange', name=str(obj.options['name'])))
-        elif obj.kind == 'notes':
+        elif obj.kind == 'document':
             self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
                 kind=obj.kind.capitalize(), color='violet', name=str(obj.options['name'])))
 
         # update the SHELL auto-completer model with the name of the new object
-        self.myKeywords.append(obj.options['name'])
         self.shell._edit.set_model_data(self.myKeywords)
 
         if autoselect:
@@ -6971,6 +6988,12 @@ class App(QtCore.QObject):
             obj_init.slots = deepcopy(obj.slots)
             obj_init.create_geometry()
 
+        def initialize_script(obj_init, app_obj):
+            obj_init.source_file = deepcopy(obj.source_file)
+
+        def initialize_document(obj_init, app_obj):
+            obj_init.source_file = deepcopy(obj.source_file)
+
         for obj in self.collection.get_selected():
             obj_name = obj.options["name"]
 
@@ -6981,6 +7004,10 @@ class App(QtCore.QObject):
                     self.new_object("gerber", str(obj_name) + "_copy", initialize)
                 elif isinstance(obj, FlatCAMGeometry):
                     self.new_object("geometry", str(obj_name) + "_copy", initialize)
+                elif isinstance(obj, FlatCAMScript):
+                    self.new_object("script", str(obj_name) + "_copy", initialize_script)
+                elif isinstance(obj, FlatCAMDocument):
+                    self.new_object("document", str(obj_name) + "_copy", initialize_document)
             except Exception as e:
                 return "Operation failed: %s" % str(e)
 
@@ -8376,6 +8403,10 @@ class App(QtCore.QObject):
             obj.on_exportgcode_button_click()
         elif type(obj) == FlatCAMGerber:
             self.on_file_savegerber()
+        elif type(obj) == FlatCAMScript:
+            self.on_file_savescript()
+        elif type(obj) == FlatCAMDocument:
+            self.on_file_savedocument()
 
     def obj_move(self):
         self.report_usage("obj_move()")
@@ -8694,6 +8725,94 @@ class App(QtCore.QObject):
                 self.file_opened.emit("Gerber", filename)
             self.file_saved.emit("Gerber", filename)
 
+    def on_file_savescript(self):
+        """
+        Callback for menu item in Project context menu.
+
+        :return: None
+        """
+        self.report_usage("on_file_savescript")
+        App.log.debug("on_file_savescript()")
+
+        obj = self.collection.get_active()
+        if obj is None:
+            self.inform.emit('[WARNING_NOTCL] %s' %
+                             _("No object selected. Please select an Script object to export."))
+            return
+
+        # Check for more compatible types and add as required
+        if not isinstance(obj, FlatCAMScript):
+            self.inform.emit('[ERROR_NOTCL] %s' %
+                             _("Failed. Only Script objects can be saved as TCL Script files..."))
+            return
+
+        name = self.collection.get_active().options["name"]
+
+        _filter = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
+        try:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+                caption="Save Script source file",
+                directory=self.get_last_save_folder() + '/' + name,
+                filter=_filter)
+        except TypeError:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Script source file"), filter=_filter)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.inform.emit('[WARNING_NOTCL] %s' %
+                             _("Save Script source file cancelled."))
+            return
+        else:
+            self.save_source_file(name, filename)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("Script", filename)
+            self.file_saved.emit("Script", filename)
+
+    def on_file_savedocument(self):
+        """
+        Callback for menu item in Project context menu.
+
+        :return: None
+        """
+        self.report_usage("on_file_savedocument")
+        App.log.debug("on_file_savedocument()")
+
+        obj = self.collection.get_active()
+        if obj is None:
+            self.inform.emit('[WARNING_NOTCL] %s' %
+                             _("No object selected. Please select an Document object to export."))
+            return
+
+        # Check for more compatible types and add as required
+        if not isinstance(obj, FlatCAMScript):
+            self.inform.emit('[ERROR_NOTCL] %s' %
+                             _("Failed. Only Document objects can be saved as Document files..."))
+            return
+
+        name = self.collection.get_active().options["name"]
+
+        _filter = "FlatCAM Documents (*.FlatDoc);;All Files (*.*)"
+        try:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+                caption="Save Document source file",
+                directory=self.get_last_save_folder() + '/' + name,
+                filter=_filter)
+        except TypeError:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Document source file"), filter=_filter)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.inform.emit('[WARNING_NOTCL] %s' %
+                             _("Save Document source file cancelled."))
+            return
+        else:
+            self.save_source_file(name, filename)
+            if self.defaults["global_open_style"] is False:
+                self.file_opened.emit("Document", filename)
+            self.file_saved.emit("Document", filename)
+
     def on_file_saveexcellon(self):
         """
         Callback for menu item in project context menu.
@@ -9090,65 +9209,22 @@ class App(QtCore.QObject):
             self.inform.emit('[success] %s' %
                              _("New TCL script file created in Code Editor."))
 
-        flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
-
-        self.proc_container.view.set_busy(_("Loading..."))
-
-        self.script_editor_tab = TextEditor(app=self)
-
-        # add the tab if it was closed
-        self.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
-        self.script_editor_tab.setObjectName('script_editor_tab')
-
         # delete the absolute and relative position and messages in the infobar
         self.ui.position_label.setText("")
         self.ui.rel_position_label.setText("")
 
-        # first clear previous text in text editor (if any)
-        self.script_editor_tab.code_editor.clear()
-        self.script_editor_tab.code_editor.setReadOnly(False)
-
-        self.script_editor_tab.code_editor.completer_enable = True
-        self.script_editor_tab.buttonRun.show()
-
-        # Switch plot_area to CNCJob tab
-        self.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
-
-        self.script_editor_tab.buttonOpen.clicked.disconnect()
-        self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt))
-        self.script_editor_tab.buttonSave.clicked.disconnect()
-        self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt))
-
-        try:
-            self.script_editor_tab.buttonRun.clicked.disconnect()
-        except TypeError:
-            pass
-        self.script_editor_tab.buttonRun.clicked.connect(self.script_editor_tab.handleRunCode)
-
-        self.script_editor_tab.handleTextChanged()
-
         if name is not None:
-            self.new_script_object(name=name)
-            script_obj = self.collection.get_by_name(name)
-        else:
-            self.new_script_object()
-            script_obj = self.collection.get_by_name('new_script')
-
-        script_text = script_obj.source_file
-
-        self.script_editor_tab.t_frame.hide()
-        if text is not None:
-            try:
-                for line in text:
-                    self.script_editor_tab.code_editor.append(line)
-            except TypeError:
-                self.script_editor_tab.code_editor.append(text)
-
+            self.new_script_object(name=name, text=text)
         else:
-            self.script_editor_tab.code_editor.append(script_text)
-        self.script_editor_tab.t_frame.show()
+            self.new_script_object(text=text)
 
-        self.proc_container.view.set_idle()
+        # script_text = script_obj.source_file
+        #
+        # self.proc_container.view.set_busy(_("Loading..."))
+        # script_obj.script_editor_tab.t_frame.hide()
+        #
+        # script_obj.script_editor_tab.t_frame.show()
+        # self.proc_container.view.set_idle()
 
     def on_fileopenscript(self, name=None, silent=False):
         """
@@ -9158,53 +9234,29 @@ class App(QtCore.QObject):
         :param name: name of a Tcl script file to open
         :return:
         """
-        script_content = []
+
+        self.report_usage("on_fileopenscript")
+        App.log.debug("on_fileopenscript()")
+
+        _filter_ = "TCL script (*.FlatScript);;TCL script (*.TCL);;TCL script (*.TXT);;All Files (*.*)"
 
         if name:
-            filename = name
+            filenames = [name]
         else:
-            _filter_ = "TCL script (*.FlatScript);;TCL script (*.TCL);;TCL script (*.TXT);;All Files (*.*)"
             try:
-                filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open TCL script"),
+                filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open TCL script"),
                                                                      directory=self.get_last_folder(), filter=_filter_)
             except TypeError:
-                filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open TCL script"), filter=_filter_)
+                filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open TCL script"), filter=_filter_)
 
-        # The Qt methods above will return a QString which can cause problems later.
-        # So far json.dump() will fail to serialize it.
-        # TODO: Improve the serialization methods and remove this fix.
-        filename = str(filename)
 
-        if filename == "":
+        if len(filenames) == 0:
             if silent is False:
                 self.inform.emit('[WARNING_NOTCL] %s' % _("Open TCL script cancelled."))
         else:
-            self.proc_container.view.set_busy(_("Loading..."))
-
-            try:
-                with open(filename, "r") as opened_script:
-                    try:
-                        for line in opened_script:
-                            QtWidgets.QApplication.processEvents()
-                            proc_line = str(line).strip('\n')
-                            script_content.append(proc_line)
-                    except Exception as e:
-                        log.debug('App.on_fileopenscript() -->%s' % str(e))
-                        if silent is False:
-                            self.inform.emit('[ERROR] %s %s' %
-                                             ('App.on_fileopenscript() -->', str(e)))
-                        return
-
-                    if silent is False:
-                        self.inform.emit('[success] %s' % _("TCL script file opened in Code Editor."))
-
-            except Exception as e:
-                log.debug("App.on_fileopenscript() -> %s" % str(e))
-
-            self.proc_container.view.set_idle()
-
-            script_name = filename.split('/')[-1].split('\\')[-1]
-            self.on_filenewscript(name=script_name, text=script_content)
+            for filename in filenames:
+                if filename != '':
+                    self.worker_task.emit({'fcn': self.open_script, 'params': [filename]})
 
     def on_filerunscript(self, name=None, silent=False):
         """
@@ -10353,8 +10405,6 @@ class App(QtCore.QObject):
             assert isinstance(app_obj_, App), \
                 "Initializer expected App, got %s" % type(app_obj_)
 
-            self.progress.emit(10)
-
             try:
                 f = open(filename)
                 gcode = f.read()
@@ -10362,20 +10412,16 @@ class App(QtCore.QObject):
             except IOError:
                 app_obj_.inform.emit('[ERROR_NOTCL] %s: %s' %
                                      (_("Failed to open"), filename))
-                self.progress.emit(0)
                 return "fail"
 
             job_obj.gcode = gcode
 
-            self.progress.emit(20)
-
             ret = job_obj.gcode_parse()
             if ret == "fail":
                 self.inform.emit('[ERROR_NOTCL] %s' %
                                  _("This is not GCODE"))
                 return "fail"
 
-            self.progress.emit(60)
             job_obj.create_geometry()
 
         with self.proc_container.new(_("Opening G-Code.")):
@@ -10398,7 +10444,44 @@ class App(QtCore.QObject):
             # GUI feedback
             self.inform.emit('[success] %s: %s' %
                              (_("Opened"), filename))
-            self.progress.emit(100)
+
+    def open_script(self, filename, outname=None, silent=False):
+        """
+        Opens a Script file, parses it and creates a new object for
+        it in the program. Thread-safe.
+
+        :param outname: Name of the resulting object. None causes the name to be that of the file.
+        :param filename: Script file filename
+        :type filename: str
+        :return: None
+        """
+        App.log.debug("open_script()")
+
+        with self.proc_container.new(_("Opening TCL Script...")):
+
+            try:
+                with open(filename, "r") as opened_script:
+                    script_content = opened_script.readlines()
+                    script_content = ''.join(script_content)
+
+                    if silent is False:
+                        self.inform.emit('[success] %s' % _("TCL script file opened in Code Editor."))
+            except Exception as e:
+                log.debug("App.open_script() -> %s" % str(e))
+                self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to open TCL Script."))
+                return
+
+            # Object name
+            script_name = outname or filename.split('/')[-1].split('\\')[-1]
+
+            # New object creation and file processing
+            self.on_filenewscript(name=script_name, text=script_content)
+
+            # Register recent file
+            self.file_opened.emit("script", filename)
+
+            # GUI feedback
+            self.inform.emit('[success] %s: %s' % (_("Opened"), filename))
 
     def open_config_file(self, filename, run_from_arg=None):
         """
@@ -10816,6 +10899,8 @@ class App(QtCore.QObject):
             "excellon": "share/drill16.png",
             'geometry': "share/geometry16.png",
             "cncjob": "share/cnc16.png",
+            "script": "share/script_new24.png",
+            "document": "share/notes16_1.png",
             "project": "share/project16.png",
             "svg": "share/geometry16.png",
             "dxf": "share/dxf16.png",
@@ -10829,6 +10914,8 @@ class App(QtCore.QObject):
             'excellon': lambda fname: self.worker_task.emit({'fcn': self.open_excellon, 'params': [fname]}),
             'geometry': lambda fname: self.worker_task.emit({'fcn': self.import_dxf, 'params': [fname]}),
             'cncjob': lambda fname: self.worker_task.emit({'fcn': self.open_gcode, 'params': [fname]}),
+            "script": lambda fname: self.worker_task.emit({'fcn': self.open_script, 'params': [fname]}),
+            "document": None,
             'project': self.open_project,
             'svg': self.import_svg,
             'dxf': self.import_dxf,

+ 157 - 13
FlatCAMObj.py

@@ -16,7 +16,10 @@ from flatcamGUI.ObjectUI import *
 from FlatCAMCommon import LoudDict
 from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
 from camlib import *
+
 import itertools
+import tkinter as tk
+import sys
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -164,11 +167,23 @@ class FlatCAMObj(QtCore.QObject):
 
         assert isinstance(self.ui, ObjectUI)
         self.ui.name_entry.returnPressed.connect(self.on_name_activate)
-        self.ui.offset_button.clicked.connect(self.on_offset_button_click)
-        self.ui.scale_button.clicked.connect(self.on_scale_button_click)
-
-        self.ui.offsetvector_entry.returnPressed.connect(self.on_offset_button_click)
-        self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
+        try:
+            # it will raise an exception for those FlatCAM objects that do not build UI with the common elements
+            self.ui.offset_button.clicked.connect(self.on_offset_button_click)
+        except (TypeError, AttributeError):
+            pass
+        try:
+            self.ui.scale_button.clicked.connect(self.on_scale_button_click)
+        except (TypeError, AttributeError):
+            pass
+        try:
+            self.ui.offsetvector_entry.returnPressed.connect(self.on_offset_button_click)
+        except (TypeError, AttributeError):
+            pass
+        try:
+            self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
+        except (TypeError, AttributeError):
+            pass
         # self.ui.skew_button.clicked.connect(self.on_skew_button_click)
 
     def build_ui(self):
@@ -6481,6 +6496,9 @@ class FlatCAMScript(FlatCAMObj):
         FlatCAMObj.set_ui(self, ui)
         FlatCAMApp.App.log.debug("FlatCAMScript.set_ui()")
 
+        assert isinstance(self.ui, ScriptObjectUI), \
+            "Expected a ScriptObjectUI, got %s" % type(self.ui)
+
         self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         if self.units == "IN":
@@ -6488,26 +6506,137 @@ class FlatCAMScript(FlatCAMObj):
         else:
             self.decimals = 2
 
+        # Fill form fields only on object create
+        self.to_form()
+
+        # Show/Hide Advanced Options
+        if self.app.defaults["global_app_level"] == 'b':
+            self.ui.level.setText(_(
+                '<span style="color:green;"><b>Basic</b></span>'
+            ))
+        else:
+            self.ui.level.setText(_(
+                '<span style="color:red;"><b>Advanced</b></span>'
+            ))
+
+        self.script_editor_tab = TextEditor(app=self.app)
+
+        # first clear previous text in text editor (if any)
+        self.script_editor_tab.code_editor.clear()
+        self.script_editor_tab.code_editor.setReadOnly(False)
+
+        self.script_editor_tab.buttonRun.show()
+
+        self.ui.autocomplete_cb.set_value(self.app.defaults['script_autocompleter'])
+        self.on_autocomplete_changed(state= self.app.defaults['script_autocompleter'])
+
+        flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
+        self.script_editor_tab.buttonOpen.clicked.disconnect()
+        self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt))
+        self.script_editor_tab.buttonSave.clicked.disconnect()
+        self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt))
+
+        self.script_editor_tab.buttonRun.clicked.connect(self.handle_run_code)
+
+        self.script_editor_tab.handleTextChanged()
+
+        self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed)
+
+        # add the source file to the Code Editor
+        for line in self.source_file.splitlines():
+            self.script_editor_tab.code_editor.append(line)
+
+        self.build_ui()
+
     def build_ui(self):
-        pass
+        FlatCAMObj.build_ui(self)
+        tab_here = False
+
+        # try to not add too many times a tab that it is already installed
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
+                tab_here = True
+                break
+
+        # add the tab if it is not already added
+        if tab_here is False:
+            self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
+            self.script_editor_tab.setObjectName(self.options['name'])
+
+        # Switch plot_area to CNCJob tab
+        self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
+
+    def handle_run_code(self):
+        # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
+        # tries to print on a hidden widget, therefore show the dock if hidden
+        if self.app.ui.shell_dock.isHidden():
+            self.app.ui.shell_dock.show()
+
+        self.script_code = deepcopy(self.script_editor_tab.code_editor.toPlainText())
+
+        old_line = ''
+        for tcl_command_line in self.script_code.splitlines():
+            # do not process lines starting with '#' = comment and empty lines
+            if not tcl_command_line.startswith('#') and tcl_command_line != '':
+                # id FlatCAM is run in Windows then replace all the slashes with
+                # the UNIX style slash that TCL understands
+                if sys.platform == 'win32':
+                    if "open" in tcl_command_line:
+                        tcl_command_line = tcl_command_line.replace('\\', '/')
+
+                if old_line != '':
+                    new_command = old_line + tcl_command_line + '\n'
+                else:
+                    new_command = tcl_command_line
+
+                # execute the actual Tcl command
+                try:
+                    self.app.shell.open_proccessing()  # Disables input box.
+
+                    result = self.app.tcl.eval(str(new_command))
+                    if result != 'None':
+                        self.app.shell.append_output(result + '\n')
+
+                    old_line = ''
+                except tk.TclError:
+                    old_line = old_line + tcl_command_line + '\n'
+                except Exception as e:
+                    log.debug("App.handleRunCode() --> %s" % str(e))
+
+        if old_line != '':
+            # it means that the script finished with an error
+            result = self.app.tcl.eval("set errorInfo")
+            log.error("Exec command Exception: %s" % (result + '\n'))
+            self.app.shell.append_error('ERROR: ' + result + '\n')
+
+        self.app.shell.close_proccessing()
+
+    def on_autocomplete_changed(self, state):
+        if state:
+            self.script_editor_tab.code_editor.completer_enable = True
+        else:
+            self.script_editor_tab.code_editor.completer_enable = False
 
 
-class FlatCAMNotes(FlatCAMObj):
+class FlatCAMDocument(FlatCAMObj):
     """
-    Represents a Notes object.
+    Represents a Document object.
     """
     optionChanged = QtCore.pyqtSignal(str)
-    ui_type = NotesObjectUI
+    ui_type = DocumentObjectUI
 
     def __init__(self, name):
-        FlatCAMApp.App.log.debug("Creating a Notes object...")
+        FlatCAMApp.App.log.debug("Creating a Document object...")
         FlatCAMObj.__init__(self, name)
 
-        self.kind = "notes"
+        self.kind = "document"
 
     def set_ui(self, ui):
         FlatCAMObj.set_ui(self, ui)
-        FlatCAMApp.App.log.debug("FlatCAMNotes.set_ui()")
+        FlatCAMApp.App.log.debug("FlatCAMDocument.set_ui()")
+
+        assert isinstance(self.ui, DocumentObjectUI), \
+            "Expected a DocumentObjectUI, got %s" % type(self.ui)
 
         self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
@@ -6516,7 +6645,22 @@ class FlatCAMNotes(FlatCAMObj):
         else:
             self.decimals = 2
 
+        # Fill form fields only on object create
+        self.to_form()
+
+        # Show/Hide Advanced Options
+        if self.app.defaults["global_app_level"] == 'b':
+            self.ui.level.setText(_(
+                '<span style="color:green;"><b>Basic</b></span>'
+            ))
+        else:
+            self.ui.level.setText(_(
+                '<span style="color:red;"><b>Advanced</b></span>'
+            ))
+
+        self.build_ui()
+
     def build_ui(self):
-        pass
+        FlatCAMObj.build_ui(self)
 
 # end of file

+ 25 - 5
ObjectCollection.py

@@ -188,7 +188,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         ("geometry", "Geometry"),
         ("cncjob", "CNC Job"),
         ("script", "Scripts"),
-        ("notes", "Notes"),
+        ("document", "Document"),
     ]
 
     classdict = {
@@ -197,7 +197,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         "cncjob": FlatCAMCNCjob,
         "geometry": FlatCAMGeometry,
         "script": FlatCAMScript,
-        "notes": FlatCAMNotes
+        "document": FlatCAMDocument
     }
 
     icon_files = {
@@ -206,7 +206,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         "cncjob": "share/cnc16.png",
         "geometry": "share/geometry16.png",
         "script": "share/script_new16.png",
-        "notes": "share/notes16_1.png"
+        "document": "share/notes16_1.png"
     }
 
     root_item = None
@@ -328,6 +328,14 @@ class ObjectCollection(QtCore.QAbstractItemModel):
                     self.app.ui.menuprojectedit.setVisible(False)
                 if type(obj) != FlatCAMGerber and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMCNCjob:
                     self.app.ui.menuprojectviewsource.setVisible(False)
+                if type(obj) != FlatCAMGerber and type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and \
+                        type(obj) != FlatCAMCNCjob:
+                    # meaning for Scripts and for Document type of FlatCAM object
+                    self.app.ui.menuprojectenable.setVisible(False)
+                    self.app.ui.menuprojectdisable.setVisible(False)
+                    self.app.ui.menuprojectedit.setVisible(False)
+                    self.app.ui.menuprojectproperties.setVisible(False)
+                    self.app.ui.menuprojectgeneratecnc.setVisible(False)
         else:
             self.app.ui.menuprojectgeneratecnc.setVisible(False)
 
@@ -576,12 +584,19 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         # send signal with the object that is deleted
         # self.app.object_status_changed.emit(active.obj, 'delete')
 
+        # some objects add a Tab on creation, close it here
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.widget(idx).objectName() == active.obj.options['name']:
+                self.app.ui.plot_tab_area.removeTab(idx)
+                break
+
         # update the SHELL auto-completer model data
         name = active.obj.options['name']
         try:
             self.app.myKeywords.remove(name)
             self.app.shell._edit.set_model_data(self.app.myKeywords)
-            self.app.ui.code_editor.set_model_data(self.app.myKeywords)
+            # this is not needed any more because now the code editor is created on demand
+            # self.app.ui.code_editor.set_model_data(self.app.myKeywords)
         except Exception as e:
             log.debug(
                 "delete_active() --> Could not remove the old object name from auto-completer model list. %s" % str(e))
@@ -742,7 +757,12 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             elif obj.kind == 'geometry':
                 self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
                     color='red', name=str(obj.options['name'])))
-
+            elif obj.kind == 'script':
+                self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
+                    color='orange', name=str(obj.options['name'])))
+            elif obj.kind == 'document':
+                self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
+                    color='violet', name=str(obj.options['name'])))
         except IndexError:
             # FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
             self.app.inform.emit('')

+ 6 - 0
README.md

@@ -15,6 +15,12 @@ CAD program, and create G-Code for Isolation routing.
 - fixed bug in Geometry Editor that did not allow the copy of geometric elements
 - created a new class that holds all the Code Editor functionality and integrated as a Editor in FlatCAM, the location is in flatcamEditors folder
 - remade all the functions for view_source, scripts and view_code to use the new TextEditor class; now all the Code Editor tabs are being kept alive, before only one could be in an open state
+- changed the name of the new object FlatCAMNotes to a more general one FlatCAMDocument
+- changed the way a new FlatCAMScript object is made, the method that is processing the Tcl commands when the Run button is clicked is moved to the FlatCAMObj.FlatCAMScript() class
+- reused the Multiprocessing Pool declared in the App for the ToolRulesCheck() class
+- adapted the Project context menu for the new types of FLatCAM objects
+- modified the setup_recent_files to accommodate the new FlatCAM objects
+- made sure that when an FlatCAM script object is deleted, it's associated Tab is closed
 
 1.10.2019
 

+ 39 - 86
flatcamEditors/FlatCAMTextEditor.py

@@ -1,11 +1,6 @@
 from flatcamGUI.GUIElements import *
 from PyQt5 import QtPrintSupport
 
-import tkinter as tk
-from copy import deepcopy
-
-import sys
-
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -39,10 +34,10 @@ class TextEditor(QtWidgets.QWidget):
 
         self.code_editor = FCTextAreaExtended()
         stylesheet = """
-                                QTextEdit { selection-background-color:yellow;
-                                            selection-color:black;
-                                }
-                             """
+                        QTextEdit { selection-background-color:yellow;
+                                    selection-color:black;
+                        }
+                     """
 
         self.code_editor.setStyleSheet(stylesheet)
 
@@ -129,7 +124,6 @@ class TextEditor(QtWidgets.QWidget):
         self.code_editor.set_model_data(self.app.myKeywords)
 
         self.gcode_edited = ''
-        self.script_code = ''
 
     def handlePrint(self):
         self.app.report_usage("handlePrint()")
@@ -269,79 +263,38 @@ class TextEditor(QtWidgets.QWidget):
         self.app.clipboard.setText(text)
         self.app.inform.emit(_("Code Editor content copied to clipboard ..."))
 
-    def handleRunCode(self):
-        # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
-        # tries to print on a hidden widget, therefore show the dock if hidden
-        if self.app.ui.shell_dock.isHidden():
-            self.app.ui.shell_dock.show()
-
-        self.script_code = deepcopy(self.code_editor.toPlainText())
-
-        old_line = ''
-        for tcl_command_line in self.app.script_code.splitlines():
-            # do not process lines starting with '#' = comment and empty lines
-            if not tcl_command_line.startswith('#') and tcl_command_line != '':
-                # id FlatCAM is run in Windows then replace all the slashes with
-                # the UNIX style slash that TCL understands
-                if sys.platform == 'win32':
-                    if "open" in tcl_command_line:
-                        tcl_command_line = tcl_command_line.replace('\\', '/')
-
-                if old_line != '':
-                    new_command = old_line + tcl_command_line + '\n'
-                else:
-                    new_command = tcl_command_line
-
-                # execute the actual Tcl command
-                try:
-                    self.app.shell.open_proccessing()  # Disables input box.
-
-                    result = self.app.tcl.eval(str(new_command))
-                    if result != 'None':
-                        self.app.shell.append_output(result + '\n')
-
-                    old_line = ''
-                except tk.TclError:
-                    old_line = old_line + tcl_command_line + '\n'
-                except Exception as e:
-                    log.debug("App.handleRunCode() --> %s" % str(e))
-
-        if old_line != '':
-            # it means that the script finished with an error
-            result = self.app.tcl.eval("set errorInfo")
-            log.error("Exec command Exception: %s" % (result + '\n'))
-            self.app.shell.append_error('ERROR: ' + result + '\n')
-
-        self.app.shell.close_proccessing()
-
-    def closeEvent(self, QCloseEvent):
-        try:
-            self.code_editor.textChanged.disconnect()
-        except TypeError:
-            pass
-        try:
-            self.buttonOpen.clicked.disconnect()
-        except TypeError:
-            pass
-        try:
-            self.buttonPrint.clicked.disconnect()
-        except TypeError:
-            pass
-        try:
-            self.buttonPreview.clicked.disconnect()
-        except TypeError:
-            pass
-        try:
-            self.buttonFind.clicked.disconnect()
-        except TypeError:
-            pass
-        try:
-            self.buttonReplace.clicked.disconnect()
-        except TypeError:
-            pass
-        try:
-            self.button_copy_all.clicked.disconnect()
-        except TypeError:
-            pass
-
-        super().closeEvent(QCloseEvent)
+    # def closeEvent(self, QCloseEvent):
+    #     try:
+    #         self.code_editor.textChanged.disconnect()
+    #     except TypeError:
+    #         pass
+    #     try:
+    #         self.buttonOpen.clicked.disconnect()
+    #     except TypeError:
+    #         pass
+    #     try:
+    #         self.buttonPrint.clicked.disconnect()
+    #     except TypeError:
+    #         pass
+    #     try:
+    #         self.buttonPreview.clicked.disconnect()
+    #     except TypeError:
+    #         pass
+    #     try:
+    #         self.buttonFind.clicked.disconnect()
+    #     except TypeError:
+    #         pass
+    #     try:
+    #         self.buttonReplace.clicked.disconnect()
+    #     except TypeError:
+    #         pass
+    #     try:
+    #         self.button_copy_all.clicked.disconnect()
+    #     except TypeError:
+    #         pass
+    #     try:
+    #         self.buttonRun.clicked.disconnect()
+    #     except TypeError:
+    #         pass
+    #
+    #     super().closeEvent(QCloseEvent)

+ 103 - 596
flatcamGUI/ObjectUI.py

@@ -30,7 +30,7 @@ class ObjectUI(QtWidgets.QWidget):
     put UI elements in ObjectUI.custom_box (QtWidgets.QLayout).
     """
 
-    def __init__(self, icon_file='share/flatcam_icon32.png', title=_('FlatCAM Object'), parent=None):
+    def __init__(self, icon_file='share/flatcam_icon32.png', title=_('FlatCAM Object'), parent=None, common=True):
         QtWidgets.QWidget.__init__(self, parent=parent)
 
         layout = QtWidgets.QVBoxLayout()
@@ -74,62 +74,62 @@ class ObjectUI(QtWidgets.QWidget):
         # ###########################
         # ## Common to all objects ##
         # ###########################
+        if common is True:
+            # ### Scale ####
+            self.scale_label = QtWidgets.QLabel('<b>%s:</b>' % _('Scale'))
+            self.scale_label.setToolTip(
+                _("Change the size of the object.")
+            )
+            layout.addWidget(self.scale_label)
 
-        # ### Scale ####
-        self.scale_label = QtWidgets.QLabel('<b>%s:</b>' % _('Scale'))
-        self.scale_label.setToolTip(
-            _("Change the size of the object.")
-        )
-        layout.addWidget(self.scale_label)
-
-        self.scale_grid = QtWidgets.QGridLayout()
-        layout.addLayout(self.scale_grid)
-
-        # Factor
-        faclabel = QtWidgets.QLabel('%s:' % _('Factor'))
-        faclabel.setToolTip(
-            _("Factor by which to multiply\n"
-              "geometric features of this object.")
-        )
-        self.scale_grid.addWidget(faclabel, 0, 0)
-        self.scale_entry = FloatEntry2()
-        self.scale_entry.set_value(1.0)
-        self.scale_grid.addWidget(self.scale_entry, 0, 1)
-
-        # GO Button
-        self.scale_button = QtWidgets.QPushButton(_('Scale'))
-        self.scale_button.setToolTip(
-            _("Perform scaling operation.")
-        )
-        self.scale_button.setMinimumWidth(70)
-        self.scale_grid.addWidget(self.scale_button, 0, 2)
+            self.scale_grid = QtWidgets.QGridLayout()
+            layout.addLayout(self.scale_grid)
 
-        # ### Offset ####
-        self.offset_label = QtWidgets.QLabel('<b>%s:</b>' % _('Offset'))
-        self.offset_label.setToolTip(
-            _("Change the position of this object.")
-        )
-        layout.addWidget(self.offset_label)
+            # Factor
+            faclabel = QtWidgets.QLabel('%s:' % _('Factor'))
+            faclabel.setToolTip(
+                _("Factor by which to multiply\n"
+                  "geometric features of this object.")
+            )
+            self.scale_grid.addWidget(faclabel, 0, 0)
+            self.scale_entry = FloatEntry2()
+            self.scale_entry.set_value(1.0)
+            self.scale_grid.addWidget(self.scale_entry, 0, 1)
+
+            # GO Button
+            self.scale_button = QtWidgets.QPushButton(_('Scale'))
+            self.scale_button.setToolTip(
+                _("Perform scaling operation.")
+            )
+            self.scale_button.setMinimumWidth(70)
+            self.scale_grid.addWidget(self.scale_button, 0, 2)
 
-        self.offset_grid = QtWidgets.QGridLayout()
-        layout.addLayout(self.offset_grid)
+            # ### Offset ####
+            self.offset_label = QtWidgets.QLabel('<b>%s:</b>' % _('Offset'))
+            self.offset_label.setToolTip(
+                _("Change the position of this object.")
+            )
+            layout.addWidget(self.offset_label)
 
-        self.offset_vectorlabel = QtWidgets.QLabel('%s:' % _('Vector'))
-        self.offset_vectorlabel.setToolTip(
-            _("Amount by which to move the object\n"
-              "in the x and y axes in (x, y) format.")
-        )
-        self.offset_grid.addWidget(self.offset_vectorlabel, 0, 0)
-        self.offsetvector_entry = EvalEntry2()
-        self.offsetvector_entry.setText("(0.0, 0.0)")
-        self.offset_grid.addWidget(self.offsetvector_entry, 0, 1)
+            self.offset_grid = QtWidgets.QGridLayout()
+            layout.addLayout(self.offset_grid)
 
-        self.offset_button = QtWidgets.QPushButton(_('Offset'))
-        self.offset_button.setToolTip(
-            _("Perform the offset operation.")
-        )
-        self.offset_button.setMinimumWidth(70)
-        self.offset_grid.addWidget(self.offset_button, 0, 2)
+            self.offset_vectorlabel = QtWidgets.QLabel('%s:' % _('Vector'))
+            self.offset_vectorlabel.setToolTip(
+                _("Amount by which to move the object\n"
+                  "in the x and y axes in (x, y) format.")
+            )
+            self.offset_grid.addWidget(self.offset_vectorlabel, 0, 0)
+            self.offsetvector_entry = EvalEntry2()
+            self.offsetvector_entry.setText("(0.0, 0.0)")
+            self.offset_grid.addWidget(self.offsetvector_entry, 0, 1)
+
+            self.offset_button = QtWidgets.QPushButton(_('Offset'))
+            self.offset_button.setToolTip(
+                _("Perform the offset operation.")
+            )
+            self.offset_button.setMinimumWidth(70)
+            self.offset_grid.addWidget(self.offset_button, 0, 2)
 
         layout.addStretch()
 
@@ -1726,573 +1726,80 @@ class ScriptObjectUI(ObjectUI):
         be placed in ``self.custom_box`` to preserve the layout.
         """
 
-        ObjectUI.__init__(self, title=_('Script Object'), icon_file='share/cnc32.png', parent=parent)
-
-        # Scale and offset ans skew are not available for CNCJob objects.
-        # Hiding from the GUI.
-        for i in range(0, self.scale_grid.count()):
-            self.scale_grid.itemAt(i).widget().hide()
-        self.scale_label.hide()
-        self.scale_button.hide()
-
-        for i in range(0, self.offset_grid.count()):
-            self.offset_grid.itemAt(i).widget().hide()
-        self.offset_label.hide()
-        self.offset_button.hide()
-
-        # ## Plot options
-        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
-        self.custom_box.addWidget(self.plot_options_label)
-
-        self.cncplot_method_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot kind"))
-        self.cncplot_method_label.setToolTip(
-            _(
-                "This selects the kind of geometries on the canvas to plot.\n"
-                "Those can be either of type 'Travel' which means the moves\n"
-                "above the work piece or it can be of type 'Cut',\n"
-                "which means the moves that cut into the material."
-            )
-        )
-
-        self.cncplot_method_combo = RadioSet([
-            {"label": _("All"), "value": "all"},
-            {"label": _("Travel"), "value": "travel"},
-            {"label": _("Cut"), "value": "cut"}
-        ], stretch=False)
-
-        self.annotation_label = QtWidgets.QLabel("<b>%s:</b>" % _("Display Annotation"))
-        self.annotation_label.setToolTip(
-            _("This selects if to display text annotation on the plot.\n"
-              "When checked it will display numbers in order for each end\n"
-              "of a travel line.")
-        )
-        self.annotation_cb = FCCheckBox()
+        ObjectUI.__init__(self, title=_('Script Object'),
+                          icon_file='share/script_new24.png',
+                          parent=parent,
+                          common=False)
 
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(self.name_hlay)
+
         name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_hlay.addWidget(name_label)
         self.name_hlay.addWidget(self.name_entry)
 
-        self.t_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _("Travelled dist."))
-        self.t_distance_label.setToolTip(
-            _("This is the total travelled distance on X-Y plane.\n"
-              "In current units.")
-        )
-        self.t_distance_entry = FCEntry()
-        self.t_distance_entry.setToolTip(
-            _("This is the total travelled distance on X-Y plane.\n"
-              "In current units.")
-        )
-        self.units_label = QtWidgets.QLabel()
+        h_lay = QtWidgets.QHBoxLayout()
+        h_lay.setAlignment(QtCore.Qt.AlignVCenter)
+        self.custom_box.addLayout(h_lay)
 
-        self.t_time_label = QtWidgets.QLabel("<b>%s:</b>" % _("Estimated time"))
-        self.t_time_label.setToolTip(
-            _("This is the estimated time to do the routing/drilling,\n"
-              "without the time spent in ToolChange events.")
-        )
-        self.t_time_entry = FCEntry()
-        self.t_time_entry.setToolTip(
-            _("This is the estimated time to do the routing/drilling,\n"
-              "without the time spent in ToolChange events.")
+        self.autocomplete_cb = FCCheckBox("%s" % _("Auto Completer"))
+        self.autocomplete_cb.setToolTip(
+            _("This selects if the auto completer is enabled in the Script Editor.")
         )
-        self.units_time_label = QtWidgets.QLabel()
-
-        f_lay = QtWidgets.QGridLayout()
-        f_lay.setColumnStretch(1, 1)
-        f_lay.setColumnStretch(2, 1)
-
-        self.custom_box.addLayout(f_lay)
-        f_lay.addWidget(self.cncplot_method_label, 0, 0)
-        f_lay.addWidget(self.cncplot_method_combo, 0, 1)
-        f_lay.addWidget(QtWidgets.QLabel(''), 0, 2)
-        f_lay.addWidget(self.annotation_label, 1, 0)
-        f_lay.addWidget(self.annotation_cb, 1, 1)
-        f_lay.addWidget(QtWidgets.QLabel(''), 1, 2)
-        f_lay.addWidget(self.t_distance_label, 2, 0)
-        f_lay.addWidget(self.t_distance_entry, 2, 1)
-        f_lay.addWidget(self.units_label, 2, 2)
-        f_lay.addWidget(self.t_time_label, 3, 0)
-        f_lay.addWidget(self.t_time_entry, 3, 1)
-        f_lay.addWidget(self.units_time_label, 3, 2)
-
-        self.t_distance_label.hide()
-        self.t_distance_entry.setVisible(False)
-        self.t_time_label.hide()
-        self.t_time_entry.setVisible(False)
-
-        e1_lbl = QtWidgets.QLabel('')
-        self.custom_box.addWidget(e1_lbl)
-
-        hlay = QtWidgets.QHBoxLayout()
-        self.custom_box.addLayout(hlay)
-
-        # CNC Tools Table for plot
-        self.cnc_tools_table_label = QtWidgets.QLabel('<b>%s</b>' % _('CNC Tools Table'))
-        self.cnc_tools_table_label.setToolTip(
-            _(
-                "Tools in this CNCJob object used for cutting.\n"
-                "The tool diameter is used for plotting on canvas.\n"
-                "The 'Offset' entry will set an offset for the cut.\n"
-                "'Offset' can be inside, outside, on path (none) and custom.\n"
-                "'Type' entry is only informative and it allow to know the \n"
-                "intent of using the current tool. \n"
-                "It can be Rough(ing), Finish(ing) or Iso(lation).\n"
-                "The 'Tool type'(TT) can be circular with 1 to 4 teeths(C1..C4),\n"
-                "ball(B), or V-Shaped(V)."
-            )
+        self.autocomplete_cb.setStyleSheet(
+            """
+            QCheckBox {font-weight: bold; color: black}
+            """
         )
-        hlay.addWidget(self.cnc_tools_table_label)
+        h_lay.addWidget(self.autocomplete_cb)
+        h_lay.addStretch()
 
-        # Plot CB
-        # self.plot_cb = QtWidgets.QCheckBox('Plot')
-        self.plot_cb = FCCheckBox(_('Plot Object'))
-        self.plot_cb.setToolTip(
-            _("Plot (show) this object.")
-        )
+        # Plot CB - this is added only for compatibility; other FlatCAM objects expect it and the mechanism is already
+        # established and I don't want to changed it right now
+        self.plot_cb = FCCheckBox()
         self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
-        hlay.addStretch()
-        hlay.addWidget(self.plot_cb)
-
-        self.cnc_tools_table = FCTable()
-        self.custom_box.addWidget(self.cnc_tools_table)
-
-        # self.cnc_tools_table.setColumnCount(4)
-        # self.cnc_tools_table.setHorizontalHeaderLabels(['#', 'Dia', 'Plot', ''])
-        # self.cnc_tools_table.setColumnHidden(3, True)
-        self.cnc_tools_table.setColumnCount(7)
-        self.cnc_tools_table.setColumnWidth(0, 20)
-        self.cnc_tools_table.setHorizontalHeaderLabels(['#', _('Dia'), _('Offset'), _('Type'), _('TT'), '',
-                                                        _('P')])
-        self.cnc_tools_table.setColumnHidden(5, True)
-        # stylesheet = "::section{Background-color:rgb(239,239,245)}"
-        # self.cnc_tools_table.horizontalHeader().setStyleSheet(stylesheet)
-
-        # Update plot button
-        self.updateplot_button = QtWidgets.QPushButton(_('Update Plot'))
-        self.updateplot_button.setToolTip(
-            _("Update the plot.")
-        )
-        self.custom_box.addWidget(self.updateplot_button)
-
-        # ####################
-        # ## Export G-Code ##
-        # ####################
-        self.export_gcode_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export CNC Code"))
-        self.export_gcode_label.setToolTip(
-            _("Export and save G-Code to\n"
-              "make this object to a file.")
-        )
-        self.custom_box.addWidget(self.export_gcode_label)
-
-        # 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.custom_box.addWidget(prependlabel)
-
-        self.prepend_text = FCTextArea()
-        self.custom_box.addWidget(self.prepend_text)
+        self.custom_box.addWidget(self.plot_cb)
+        self.plot_cb.hide()
 
-        # 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.custom_box.addWidget(appendlabel)
+        self.custom_box.addStretch()
 
-        self.append_text = FCTextArea()
-        self.custom_box.addWidget(self.append_text)
 
-        self.cnc_frame = QtWidgets.QFrame()
-        self.cnc_frame.setContentsMargins(0, 0, 0, 0)
-        self.custom_box.addWidget(self.cnc_frame)
-        self.cnc_box = QtWidgets.QVBoxLayout()
-        self.cnc_box.setContentsMargins(0, 0, 0, 0)
-        self.cnc_frame.setLayout(self.cnc_box)
+class DocumentObjectUI(ObjectUI):
+    """
+    User interface for Notes objects.
+    """
 
-        # Toolchange Custom G-Code
-        self.toolchangelabel = QtWidgets.QLabel('%s:' % _('Toolchange G-Code'))
-        self.toolchangelabel.setToolTip(
-            _(
-                "Type here any G-Code commands you would\n"
-                "like to be executed when Toolchange event is encountered.\n"
-                "This will constitute a Custom Toolchange GCode,\n"
-                "or a Toolchange Macro.\n"
-                "The FlatCAM variables are surrounded by '%' symbol.\n\n"
-                "WARNING: it can be used only with a postprocessor file\n"
-                "that has 'toolchange_custom' in it's name and this is built\n"
-                "having as template the 'Toolchange Custom' posprocessor file."
-            )
-        )
-        self.cnc_box.addWidget(self.toolchangelabel)
+    def __init__(self, parent=None):
+        """
+        Creates the user interface for Notes objects. GUI elements should
+        be placed in ``self.custom_box`` to preserve the layout.
+        """
 
-        self.toolchange_text = FCTextArea()
-        self.cnc_box.addWidget(self.toolchange_text)
+        ObjectUI.__init__(self, title=_('Document Object'),
+                          icon_file='share/notes16_1.png',
+                          parent=parent,
+                          common=False)
 
-        cnclay = QtWidgets.QHBoxLayout()
-        self.cnc_box.addLayout(cnclay)
+        # ## Object name
+        self.name_hlay = QtWidgets.QHBoxLayout()
+        self.custom_box.addLayout(self.name_hlay)
 
-        # Toolchange Replacement Enable
-        self.toolchange_cb = FCCheckBox(label='%s' % _('Use Toolchange Macro'))
-        self.toolchange_cb.setToolTip(
-            _("Check this box if you want to use\n"
-              "a Custom Toolchange GCode (macro).")
-        )
+        name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
+        self.name_entry = FCEntry()
+        self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
+        self.name_hlay.addWidget(name_label)
+        self.name_hlay.addWidget(self.name_entry)
 
-        # Variable list
-        self.tc_variable_combo = FCComboBox()
-        self.tc_variable_combo.setToolTip(
-            _(
-                "A list of the FlatCAM variables that can be used\n"
-                "in the Toolchange event.\n"
-                "They have to be surrounded by the '%' symbol"
-            )
-        )
+        # Plot CB - this is added only for compatibility; other FlatCAM objects expect it and the mechanism is already
+        # established and I don't want to changed it right now
+        self.plot_cb = FCCheckBox()
+        self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
+        self.custom_box.addWidget(self.plot_cb)
+        self.plot_cb.hide()
 
-        # Populate the Combo Box
-        variables = [_('Parameters'), 'tool', 'tooldia', 't_drills', 'x_toolchange', 'y_toolchange', 'z_toolchange',
-                     'z_cut', 'z_move', 'z_depthpercut', 'spindlespeed', 'dwelltime']
-        self.tc_variable_combo.addItems(variables)
-        self.tc_variable_combo.setItemData(0, _("FlatCAM CNC parameters"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(1, _("tool = tool number"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(2, _("tooldia = tool diameter"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(3, _("t_drills = for Excellon, total number of drills"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(4, _("x_toolchange = X coord for Toolchange"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(5, _("y_toolchange = Y coord for Toolchange"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(6, _("z_toolchange = Z coord for Toolchange"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(7, _("z_cut = depth where to cut"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(8, _("z_move = height where to travel"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(9, _("z_depthpercut = the step value for multidepth cut"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(10, _("spindlesspeed = the value for the spindle speed"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(11, _("dwelltime = time to dwell to allow the "
-                                                 "spindle to reach it's set RPM"),
-                                           Qt.ToolTipRole)
-
-        cnclay.addWidget(self.toolchange_cb)
-        cnclay.addStretch()
-        cnclay.addWidget(self.tc_variable_combo)
-
-        self.toolch_ois = OptionalInputSection(self.toolchange_cb,
-                                               [self.toolchangelabel, self.toolchange_text, self.tc_variable_combo])
-
-        h_lay = QtWidgets.QHBoxLayout()
-        h_lay.setAlignment(QtCore.Qt.AlignVCenter)
-        self.custom_box.addLayout(h_lay)
-
-        # Edit GCode Button
-        self.modify_gcode_button = QtWidgets.QPushButton(_('View CNC Code'))
-        self.modify_gcode_button.setToolTip(
-            _("Opens TAB to view/modify/print G-Code\n"
-              "file.")
-        )
-
-        # GO Button
-        self.export_gcode_button = QtWidgets.QPushButton(_('Save CNC Code'))
-        self.export_gcode_button.setToolTip(
-            _("Opens dialog to save G-Code\n"
-              "file.")
-        )
-
-        h_lay.addWidget(self.modify_gcode_button)
-        h_lay.addWidget(self.export_gcode_button)
-        # self.custom_box.addWidget(self.export_gcode_button)
-
-class NotesObjectUI(ObjectUI):
-    """
-    User interface for Notes objects.
-    """
-
-    def __init__(self, parent=None):
-        """
-        Creates the user interface for Notes objects. GUI elements should
-        be placed in ``self.custom_box`` to preserve the layout.
-        """
-
-        ObjectUI.__init__(self, title=_('Notes Object'), icon_file='share/cnc32.png', parent=parent)
-
-        # Scale and offset ans skew are not available for CNCJob objects.
-        # Hiding from the GUI.
-        for i in range(0, self.scale_grid.count()):
-            self.scale_grid.itemAt(i).widget().hide()
-        self.scale_label.hide()
-        self.scale_button.hide()
-
-        for i in range(0, self.offset_grid.count()):
-            self.offset_grid.itemAt(i).widget().hide()
-        self.offset_label.hide()
-        self.offset_button.hide()
-
-        # ## Plot options
-        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
-        self.custom_box.addWidget(self.plot_options_label)
-
-        self.cncplot_method_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot kind"))
-        self.cncplot_method_label.setToolTip(
-            _(
-                "This selects the kind of geometries on the canvas to plot.\n"
-                "Those can be either of type 'Travel' which means the moves\n"
-                "above the work piece or it can be of type 'Cut',\n"
-                "which means the moves that cut into the material."
-            )
-        )
-
-        self.cncplot_method_combo = RadioSet([
-            {"label": _("All"), "value": "all"},
-            {"label": _("Travel"), "value": "travel"},
-            {"label": _("Cut"), "value": "cut"}
-        ], stretch=False)
-
-        self.annotation_label = QtWidgets.QLabel("<b>%s:</b>" % _("Display Annotation"))
-        self.annotation_label.setToolTip(
-            _("This selects if to display text annotation on the plot.\n"
-              "When checked it will display numbers in order for each end\n"
-              "of a travel line.")
-        )
-        self.annotation_cb = FCCheckBox()
-
-        # ## Object name
-        self.name_hlay = QtWidgets.QHBoxLayout()
-        self.custom_box.addLayout(self.name_hlay)
-        name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
-        self.name_entry = FCEntry()
-        self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
-        self.name_hlay.addWidget(name_label)
-        self.name_hlay.addWidget(self.name_entry)
-
-        self.t_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _("Travelled dist."))
-        self.t_distance_label.setToolTip(
-            _("This is the total travelled distance on X-Y plane.\n"
-              "In current units.")
-        )
-        self.t_distance_entry = FCEntry()
-        self.t_distance_entry.setToolTip(
-            _("This is the total travelled distance on X-Y plane.\n"
-              "In current units.")
-        )
-        self.units_label = QtWidgets.QLabel()
-
-        self.t_time_label = QtWidgets.QLabel("<b>%s:</b>" % _("Estimated time"))
-        self.t_time_label.setToolTip(
-            _("This is the estimated time to do the routing/drilling,\n"
-              "without the time spent in ToolChange events.")
-        )
-        self.t_time_entry = FCEntry()
-        self.t_time_entry.setToolTip(
-            _("This is the estimated time to do the routing/drilling,\n"
-              "without the time spent in ToolChange events.")
-        )
-        self.units_time_label = QtWidgets.QLabel()
-
-        f_lay = QtWidgets.QGridLayout()
-        f_lay.setColumnStretch(1, 1)
-        f_lay.setColumnStretch(2, 1)
-
-        self.custom_box.addLayout(f_lay)
-        f_lay.addWidget(self.cncplot_method_label, 0, 0)
-        f_lay.addWidget(self.cncplot_method_combo, 0, 1)
-        f_lay.addWidget(QtWidgets.QLabel(''), 0, 2)
-        f_lay.addWidget(self.annotation_label, 1, 0)
-        f_lay.addWidget(self.annotation_cb, 1, 1)
-        f_lay.addWidget(QtWidgets.QLabel(''), 1, 2)
-        f_lay.addWidget(self.t_distance_label, 2, 0)
-        f_lay.addWidget(self.t_distance_entry, 2, 1)
-        f_lay.addWidget(self.units_label, 2, 2)
-        f_lay.addWidget(self.t_time_label, 3, 0)
-        f_lay.addWidget(self.t_time_entry, 3, 1)
-        f_lay.addWidget(self.units_time_label, 3, 2)
-
-        self.t_distance_label.hide()
-        self.t_distance_entry.setVisible(False)
-        self.t_time_label.hide()
-        self.t_time_entry.setVisible(False)
-
-        e1_lbl = QtWidgets.QLabel('')
-        self.custom_box.addWidget(e1_lbl)
-
-        hlay = QtWidgets.QHBoxLayout()
-        self.custom_box.addLayout(hlay)
-
-        # CNC Tools Table for plot
-        self.cnc_tools_table_label = QtWidgets.QLabel('<b>%s</b>' % _('CNC Tools Table'))
-        self.cnc_tools_table_label.setToolTip(
-            _(
-                "Tools in this CNCJob object used for cutting.\n"
-                "The tool diameter is used for plotting on canvas.\n"
-                "The 'Offset' entry will set an offset for the cut.\n"
-                "'Offset' can be inside, outside, on path (none) and custom.\n"
-                "'Type' entry is only informative and it allow to know the \n"
-                "intent of using the current tool. \n"
-                "It can be Rough(ing), Finish(ing) or Iso(lation).\n"
-                "The 'Tool type'(TT) can be circular with 1 to 4 teeths(C1..C4),\n"
-                "ball(B), or V-Shaped(V)."
-            )
-        )
-        hlay.addWidget(self.cnc_tools_table_label)
-
-        # Plot CB
-        # self.plot_cb = QtWidgets.QCheckBox('Plot')
-        self.plot_cb = FCCheckBox(_('Plot Object'))
-        self.plot_cb.setToolTip(
-            _("Plot (show) this object.")
-        )
-        self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
-        hlay.addStretch()
-        hlay.addWidget(self.plot_cb)
-
-        self.cnc_tools_table = FCTable()
-        self.custom_box.addWidget(self.cnc_tools_table)
-
-        # self.cnc_tools_table.setColumnCount(4)
-        # self.cnc_tools_table.setHorizontalHeaderLabels(['#', 'Dia', 'Plot', ''])
-        # self.cnc_tools_table.setColumnHidden(3, True)
-        self.cnc_tools_table.setColumnCount(7)
-        self.cnc_tools_table.setColumnWidth(0, 20)
-        self.cnc_tools_table.setHorizontalHeaderLabels(['#', _('Dia'), _('Offset'), _('Type'), _('TT'), '',
-                                                        _('P')])
-        self.cnc_tools_table.setColumnHidden(5, True)
-        # stylesheet = "::section{Background-color:rgb(239,239,245)}"
-        # self.cnc_tools_table.horizontalHeader().setStyleSheet(stylesheet)
-
-        # Update plot button
-        self.updateplot_button = QtWidgets.QPushButton(_('Update Plot'))
-        self.updateplot_button.setToolTip(
-            _("Update the plot.")
-        )
-        self.custom_box.addWidget(self.updateplot_button)
-
-        # ####################
-        # ## Export G-Code ##
-        # ####################
-        self.export_gcode_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export CNC Code"))
-        self.export_gcode_label.setToolTip(
-            _("Export and save G-Code to\n"
-              "make this object to a file.")
-        )
-        self.custom_box.addWidget(self.export_gcode_label)
-
-        # 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.custom_box.addWidget(prependlabel)
-
-        self.prepend_text = FCTextArea()
-        self.custom_box.addWidget(self.prepend_text)
-
-        # 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.custom_box.addWidget(appendlabel)
-
-        self.append_text = FCTextArea()
-        self.custom_box.addWidget(self.append_text)
-
-        self.cnc_frame = QtWidgets.QFrame()
-        self.cnc_frame.setContentsMargins(0, 0, 0, 0)
-        self.custom_box.addWidget(self.cnc_frame)
-        self.cnc_box = QtWidgets.QVBoxLayout()
-        self.cnc_box.setContentsMargins(0, 0, 0, 0)
-        self.cnc_frame.setLayout(self.cnc_box)
-
-        # Toolchange Custom G-Code
-        self.toolchangelabel = QtWidgets.QLabel('%s:' % _('Toolchange G-Code'))
-        self.toolchangelabel.setToolTip(
-            _(
-                "Type here any G-Code commands you would\n"
-                "like to be executed when Toolchange event is encountered.\n"
-                "This will constitute a Custom Toolchange GCode,\n"
-                "or a Toolchange Macro.\n"
-                "The FlatCAM variables are surrounded by '%' symbol.\n\n"
-                "WARNING: it can be used only with a postprocessor file\n"
-                "that has 'toolchange_custom' in it's name and this is built\n"
-                "having as template the 'Toolchange Custom' posprocessor file."
-            )
-        )
-        self.cnc_box.addWidget(self.toolchangelabel)
-
-        self.toolchange_text = FCTextArea()
-        self.cnc_box.addWidget(self.toolchange_text)
-
-        cnclay = QtWidgets.QHBoxLayout()
-        self.cnc_box.addLayout(cnclay)
-
-        # Toolchange Replacement Enable
-        self.toolchange_cb = FCCheckBox(label='%s' % _('Use Toolchange Macro'))
-        self.toolchange_cb.setToolTip(
-            _("Check this box if you want to use\n"
-              "a Custom Toolchange GCode (macro).")
-        )
-
-        # Variable list
-        self.tc_variable_combo = FCComboBox()
-        self.tc_variable_combo.setToolTip(
-            _(
-                "A list of the FlatCAM variables that can be used\n"
-                "in the Toolchange event.\n"
-                "They have to be surrounded by the '%' symbol"
-            )
-        )
-
-        # Populate the Combo Box
-        variables = [_('Parameters'), 'tool', 'tooldia', 't_drills', 'x_toolchange', 'y_toolchange', 'z_toolchange',
-                     'z_cut', 'z_move', 'z_depthpercut', 'spindlespeed', 'dwelltime']
-        self.tc_variable_combo.addItems(variables)
-        self.tc_variable_combo.setItemData(0, _("FlatCAM CNC parameters"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(1, _("tool = tool number"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(2, _("tooldia = tool diameter"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(3, _("t_drills = for Excellon, total number of drills"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(4, _("x_toolchange = X coord for Toolchange"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(5, _("y_toolchange = Y coord for Toolchange"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(6, _("z_toolchange = Z coord for Toolchange"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(7, _("z_cut = depth where to cut"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(8, _("z_move = height where to travel"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(9, _("z_depthpercut = the step value for multidepth cut"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(10, _("spindlesspeed = the value for the spindle speed"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(11, _("dwelltime = time to dwell to allow the "
-                                                 "spindle to reach it's set RPM"),
-                                           Qt.ToolTipRole)
-
-        cnclay.addWidget(self.toolchange_cb)
-        cnclay.addStretch()
-        cnclay.addWidget(self.tc_variable_combo)
-
-        self.toolch_ois = OptionalInputSection(self.toolchange_cb,
-                                               [self.toolchangelabel, self.toolchange_text, self.tc_variable_combo])
-
-        h_lay = QtWidgets.QHBoxLayout()
-        h_lay.setAlignment(QtCore.Qt.AlignVCenter)
-        self.custom_box.addLayout(h_lay)
-
-        # Edit GCode Button
-        self.modify_gcode_button = QtWidgets.QPushButton(_('View CNC Code'))
-        self.modify_gcode_button.setToolTip(
-            _("Opens TAB to view/modify/print G-Code\n"
-              "file.")
-        )
-
-        # GO Button
-        self.export_gcode_button = QtWidgets.QPushButton(_('Save CNC Code'))
-        self.export_gcode_button.setToolTip(
-            _("Opens dialog to save G-Code\n"
-              "file.")
-        )
-
-        h_lay.addWidget(self.modify_gcode_button)
-        h_lay.addWidget(self.export_gcode_button)
-        # self.custom_box.addWidget(self.export_gcode_button)
+        self.custom_box.addStretch()
 
 # end of file

+ 1 - 1
flatcamTools/ToolRulesCheck.py

@@ -486,7 +486,7 @@ class RulesCheck(FlatCAMTool):
         self.constrain_flag = False
 
         # Multiprocessing Process Pool
-        self.pool = Pool(processes=cpu_count())
+        self.pool = self.app.pool
         self.results = None
 
     # def on_object_loaded(self, index, row):

+ 13 - 5
tclCommands/TclCommandCncjob.py

@@ -47,7 +47,7 @@ class TclCommandCncjob(TclCommandSignaled):
     ])
 
     # array of mandatory options for current Tcl command: required = {'name','outname'}
-    required = ['name']
+    required = []
 
     # structured help for current command, args needs to be ordered
     help = {
@@ -88,16 +88,24 @@ class TclCommandCncjob(TclCommandSignaled):
         :return: None or exception
         """
 
-        name = args['name']
-
-        if 'outname' not in args:
-            args['outname'] = str(name) + "_cnc"
+        name = ''
 
         if 'muted' in args:
             muted = args['muted']
         else:
             muted = 0
 
+        try:
+            name = args['name']
+        except KeyError:
+            if muted == 0:
+                self.raise_tcl_error("Object name is missing")
+            else:
+                return "fail"
+
+        if 'outname' not in args:
+            args['outname'] = str(name) + "_cnc"
+
         obj = self.app.collection.get_by_name(str(name), isCaseSensitive=False)
 
         if obj is None: