فهرست منبع

jpcgt/flatcam/Beta слито с Beta

Camellan 6 سال پیش
والد
کامیت
2322b8ca57

+ 71 - 96
FlatCAMApp.py

@@ -20,7 +20,6 @@ import shutil
 import stat
 
 from stat import S_IREAD, S_IRGRP, S_IROTH
-import subprocess
 import ctypes
 
 # import tkinter as tk
@@ -39,7 +38,7 @@ import gc
 from xml.dom.minidom import parseString as parse_xml_string
 
 from multiprocessing.connection import Listener, Client
-from multiprocessing import Pool, cpu_count
+from multiprocessing import Pool
 import socket
 from array import array
 
@@ -241,6 +240,9 @@ class App(QtCore.QObject):
     # signal emitted when jumping
     jump_signal = pyqtSignal(tuple)
 
+    # close app signal
+    close_app_signal = pyqtSignal()
+
     def __init__(self, user_defaults=True):
         """
         Starts the application.
@@ -253,18 +255,11 @@ class App(QtCore.QObject):
 
         self.main_thread = QtWidgets.QApplication.instance().thread()
 
-        # ############################################################################
-        # # ################# OS-specific ############################################
-        # ############################################################################
-        portable = False
-
-        # Folder for user settings.
-        if sys.platform == 'win32':
-
-            # #########################################################################
-            # Setup the listening thread for another instance launching with args #####
-            # #########################################################################
+        # #########################################################################
+        # Setup the listening thread for another instance launching with args #####
+        # #########################################################################
 
+        if sys.platform == 'win32' or sys.platform == 'linux':
             # make sure the thread is stored by using a self. otherwise it's garbage collected
             self.th = QtCore.QThread()
             self.th.start(priority=QtCore.QThread.LowestPriority)
@@ -274,6 +269,13 @@ class App(QtCore.QObject):
             self.new_launch.moveToThread(self.th)
             self.new_launch.start.emit()
 
+        # ############################################################################
+        # # ################# OS-specific ############################################
+        # ############################################################################
+        portable = False
+
+        # Folder for user settings.
+        if sys.platform == 'win32':
             from win32com.shell import shell, shellcon
             if platform.architecture()[0] == '32bit':
                 App.log.debug("Win32!")
@@ -338,13 +340,23 @@ class App(QtCore.QObject):
             os.makedirs(self.preprocessorpaths)
             App.log.debug('Created preprocessors folder: ' + self.preprocessorpaths)
 
-        # create tools_db.FlatDB file if there is none
+        # create geo_tools_db.FlatDB file if there is none
+        try:
+            f = open(self.data_path + '/geo_tools_db.FlatDB')
+            f.close()
+        except IOError:
+            App.log.debug('Creating empty geo_tool_db.FlatDB')
+            f = open(self.data_path + '/geo_tools_db.FlatDB', 'w')
+            json.dump({}, f)
+            f.close()
+
+        # create fctool_tools_db.FlatDB file if there is none
         try:
-            f = open(self.data_path + '/tools_db.FlatDB')
+            f = open(self.data_path + '/fctool_tools_db.FlatDB')
             f.close()
         except IOError:
-            App.log.debug('Creating empty tool_db.FlatDB')
-            f = open(self.data_path + '/tools_db.FlatDB', 'w')
+            App.log.debug('Creating empty fctool_tool_db.FlatDB')
+            f = open(self.data_path + '/fctool_tools_db.FlatDB', 'w')
             json.dump({}, f)
             f.close()
 
@@ -955,13 +967,15 @@ class App(QtCore.QObject):
                                           'Repetier, Roland_MDX_20, Users, Toolchange_Custom, Toolchange_Probe_MACH3, '
                                           'Toolchange_manual, Users, all, angle_x, angle_y, axis, auto, axisoffset, '
                                           'box, center_x, center_y, columns, combine, connect, contour, default, '
-                                          'depthperpass, dia, diatol, dist, drilled_dias, drillz, dwell, dwelltime, '
+                                          'depthperpass, dia, diatol, dist, drilled_dias, drillz, dwelltime, '
+                                          'extracut_length, '
                                           'feedrate_z, grbl_11, grbl_laser, gridoffsety, gridx, gridy, has_offset, '
                                           'holes, hpgl, iso_type, line_xyz, margin, marlin, method, milled_dias, '
-                                          'minoffset, multidepth, name, offset, opt_type, order, outname, overlap, '
+                                          'minoffset, 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, tooldia, use_threads, value, x, x0, x1, y, y0, y1, z_cut, '
+                                          'toolchange_xy, toolchangez, '
+                                          'tooldia, use_threads, value, x, x0, x1, y, y0, y1, z_cut, '
                                           'z_move',
             "script_autocompleter": True,
             "script_text": "",
@@ -2012,8 +2026,6 @@ class App(QtCore.QObject):
         self.ui.pref_close_button.clicked.connect(self.on_pref_close_button)
 
         self.ui.pref_defaults_button.clicked.connect(self.on_restore_defaults_preferences)
-        self.ui.pref_open_button.clicked.connect(self.on_preferences_open_folder)
-        self.ui.clear_btn.clicked.connect(self.on_gui_clear)
 
         # #############################################################################
         # ######################### GUI PREFERENCES SIGNALS ###########################
@@ -2137,6 +2149,8 @@ class App(QtCore.QObject):
 
         self.ui.grid_snap_btn.triggered.connect(self.on_grid_snap_triggered)
 
+        # signal to close the application
+        self.close_app_signal.connect(self.kill_app)
         # #####################################################################################
         # ########### FINISHED CONNECTING SIGNALS #############################################
         # #####################################################################################
@@ -2187,14 +2201,16 @@ class App(QtCore.QObject):
                                  'Toolchange_manual', 'Users', 'all', 'angle_x', 'angle_y', 'auto', 'axis',
                                  'axisoffset',
                                  'box', 'center_x', 'center_y', 'columns', 'combine', 'connect', 'contour', 'default',
-                                 'depthperpass', 'dia', 'diatol', 'dist', 'drilled_dias', 'drillz', 'dwell',
-                                 'dwelltime', 'feedrate_z', 'grbl_11', 'grbl_laser', 'gridoffsety', 'gridx', 'gridy',
+                                 'depthperpass', 'dia', 'diatol', 'dist', 'drilled_dias', 'drillz',
+                                 'dwelltime', 'extracut_length',
+                                 'feedrate_z', 'grbl_11', 'grbl_laser', 'gridoffsety', 'gridx', 'gridy',
                                  'has_offset', 'holes', 'hpgl', 'iso_type', 'line_xyz', 'margin', 'marlin', 'method',
-                                 'milled_dias', 'minoffset', 'multidepth', 'name', 'offset', 'opt_type', 'order',
+                                 'milled_dias', 'minoffset', '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', 'tooldia', 'use_threads', 'value',
+                                 'spacing_rows', 'spindlespeed', 'toolchange_xy', 'toolchangez',
+                                 'tooldia', 'use_threads', 'value',
                                  'x', 'x0', 'x1', 'y', 'y0', 'y1', 'z_cut', 'z_move'
                                  ]
 
@@ -2696,20 +2712,24 @@ class App(QtCore.QObject):
                 sys.exit(2)
 
         if self.cmd_line_shellfile:
-            try:
+            if self.cmd_line_headless != 1:
                 if self.ui.shell_dock.isHidden():
                     self.ui.shell_dock.show()
-
+            try:
                 with open(self.cmd_line_shellfile, "r") as myfile:
-                    if show_splash:
-                        self.splash.showMessage('%s: %ssec\n%s' % (
-                            _("Canvas initialization started.\n"
-                              "Canvas initialization finished in"), '%.2f' % self.used_time,
-                            _("Executing Tcl Script ...")),
-                                                alignment=Qt.AlignBottom | Qt.AlignLeft,
-                                                color=QtGui.QColor("gray"))
+                    # if show_splash:
+                    #     self.splash.showMessage('%s: %ssec\n%s' % (
+                    #         _("Canvas initialization started.\n"
+                    #           "Canvas initialization finished in"), '%.2f' % self.used_time,
+                    #         _("Executing Tcl Script ...")),
+                    #                             alignment=Qt.AlignBottom | Qt.AlignLeft,
+                    #                             color=QtGui.QColor("gray"))
                     cmd_line_shellfile_text = myfile.read()
-                    self.shell._sysShell.exec_command(cmd_line_shellfile_text)
+                    if self.cmd_line_headless != 1:
+                        self.shell._sysShell.exec_command(cmd_line_shellfile_text)
+                    else:
+                        self.shell._sysShell.exec_command(cmd_line_shellfile_text, no_echo=True)
+
             except Exception as ext:
                 print("ERROR: ", ext)
                 sys.exit(2)
@@ -3597,7 +3617,7 @@ class App(QtCore.QObject):
         Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
 
         :param text: Input command
-        :param reraise: Re-raise TclError exceptions in Python (mostly for unitttests).
+        :param reraise: Re-raise TclError exceptions in Python (mostly for unittests).
         :param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
         will create crashes of the _Expandable_Edit widget
         :return: Output from the command
@@ -3607,13 +3627,14 @@ class App(QtCore.QObject):
 
         try:
             if no_echo is False:
-                self.shell.open_proccessing()  # Disables input box.
+                self.shell.open_processing()  # Disables input box.
 
             result = self.tcl.eval(str(tcl_command_string))
             if result != 'None' and no_echo is False:
                 self.shell.append_output(result + '\n')
 
         except tk.TclError as e:
+
             # This will display more precise answer if something in TCL shell fails
             result = self.tcl.eval("set errorInfo")
             self.log.error("Exec command Exception: %s" % (result + '\n'))
@@ -3624,7 +3645,7 @@ class App(QtCore.QObject):
                 raise e
         finally:
             if no_echo is False:
-                self.shell.close_proccessing()
+                self.shell.close_processing()
             pass
         return result
 
@@ -3978,58 +3999,13 @@ class App(QtCore.QObject):
                 json.dump(defaults_from_file, f, default=to_dict, indent=2, sort_keys=True)
                 f.close()
             except Exception:
-                self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write defaults to file."))
+                self.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed to write defaults to file."), str(filename)))
                 return
         if self.defaults["global_open_style"] is False:
             self.file_opened.emit("preferences", filename)
         self.file_saved.emit("preferences", filename)
         self.inform.emit('[success] %s: %s' % (_("Exported preferences to"), filename))
 
-    def on_preferences_open_folder(self):
-        """
-        Will open an Explorer window set to the folder path where the FlatCAM preferences files are usually saved.
-
-        :return: None
-        """
-        self.report_usage("on_preferences_open_folder()")
-
-        if sys.platform == 'win32':
-            subprocess.Popen('explorer %s' % self.data_path)
-        elif sys.platform == 'darwin':
-            os.system('open "%s"' % self.data_path)
-        else:
-            subprocess.Popen(['xdg-open', self.data_path])
-        self.inform.emit('[success] %s' %
-                         _("FlatCAM Preferences Folder opened."))
-
-    def on_gui_clear(self):
-        theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
-        theme_settings.setValue('theme', 'white')
-
-        del theme_settings
-
-        resource_loc = 'share'
-
-        msgbox = QtWidgets.QMessageBox()
-        msgbox.setText(_("Are you sure you want to delete the GUI Settings? "
-                         "\n")
-                       )
-        msgbox.setWindowTitle(_("Clear GUI Settings"))
-        msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/trash32.png'))
-        bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
-        bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
-
-        msgbox.setDefaultButton(bt_no)
-        msgbox.exec_()
-        response = msgbox.clickedButton()
-
-        if response == bt_yes:
-            settings = QSettings("Open Source", "FlatCAM")
-            for key in settings.allKeys():
-                settings.remove(key)
-            # This will write the setting to the platform specific storage.
-            del settings
-
     def save_geometry(self, x, y, width, height, notebook_width):
         """
         Will save the application geometry and positions in the defaults discitionary to be restored at the next
@@ -4981,13 +4957,14 @@ class App(QtCore.QObject):
             self.defaults["global_toolbar_view"] = tb_status
 
         # Save update options
+        filename = data_path + "/current_defaults.FlatConfig"
         try:
-            f = open(data_path + "/current_defaults.FlatConfig", "w")
+            f = open(filename, "w")
             json.dump(defaults, f, default=to_dict, indent=2, sort_keys=True)
             f.close()
         except Exception as e:
             log.debug("App.save_defaults() --> %s" % str(e))
-            self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write defaults to file."))
+            self.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed to write defaults to file."), str(filename)))
             return
 
         if not silent:
@@ -5138,14 +5115,13 @@ class App(QtCore.QObject):
             del stgs
 
         log.debug("App.final_save() --> App UI state saved.")
+        self.close_app_signal.emit()
 
-        # QtWidgets.qApp.quit()
-        QtCore.QCoreApplication.exit()
-        if sys.platform != 'win32':
-            try:
-                sys.exit()
-            except Exception:
-                pass
+    def kill_app(self):
+        QtWidgets.qApp.quit()
+        # When the main event loop is not started yet in which case the qApp.quit() will do nothing
+        # we use the following command
+        sys.exit(0)
 
     def on_portable_checked(self, state):
         """
@@ -10050,8 +10026,7 @@ class App(QtCore.QObject):
 
         if filename == "":
             if silent is False:
-                self.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Run TCL script cancelled."))
+                self.inform.emit('[WARNING_NOTCL] %s' % _("Run TCL script cancelled."))
         else:
             if self.cmd_line_headless != 1:
                 if self.ui.shell_dock.isHidden():

+ 2 - 2
FlatCAMCommon.py

@@ -737,7 +737,7 @@ class ToolsDB(QtWidgets.QWidget):
               "A position on Z plane to move immediately after job stop."))
 
     def setup_db_ui(self):
-        filename = self.app.data_path + '/tools_db.FlatDB'
+        filename = self.app.data_path + '/geo_tools_db.FlatDB'
 
         # load the database tools from the file
         try:
@@ -1174,7 +1174,7 @@ class ToolsDB(QtWidgets.QWidget):
     def on_save_tools_db(self, silent=False):
         self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.")
 
-        filename = self.app.data_path + "/tools_db.FlatDB"
+        filename = self.app.data_path + "/geo_tools_db.FlatDB"
 
         # Preferences save, update the color of the Tools DB Tab text
         for idx in range(self.app.ui.plot_tab_area.count()):

+ 2 - 2
FlatCAMObj.py

@@ -7252,7 +7252,7 @@ class FlatCAMScript(FlatCAMObj):
 
                 # execute the actual Tcl command
                 try:
-                    self.app.shell.open_proccessing()  # Disables input box.
+                    self.app.shell.open_processing()  # Disables input box.
 
                     result = self.app.tcl.eval(str(new_command))
                     if result != 'None':
@@ -7270,7 +7270,7 @@ class FlatCAMScript(FlatCAMObj):
             log.error("Exec command Exception: %s" % (result + '\n'))
             self.app.shell.append_error('ERROR: ' + result + '\n')
 
-        self.app.shell.close_proccessing()
+        self.app.shell.close_processing()
 
     def on_autocomplete_changed(self, state):
         if state:

+ 24 - 4
README.md

@@ -9,18 +9,38 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+2.01.2020
+
+- started to rework the NCC Tool GUI in preparation for adding a Tool DB feature
+- for auto-completer, now clicking an entry in the completer popup will select that entry and insert it
+- made available only for Linux and Windows (not OSX) the starting of the thread that checks if another instance of FlatCAM is already running at the launch of FLatCAM
+
+1.01.2020
+
+- fixed bug in NCC Tool: after trying to add a tool already in the Tool Table when trying to change the Tool Type the GUI does not change
+- final fix for app not quiting when running a script as argument, script that has the quit_flatcam Tcl command; fixed issue #360
+- fixed issue #363. The Tcl command drillcncjob does not create tool cut, does not allow creation of gcode, it forces the usage of dwell and dwelltime parameters
+- in NCC Tool I've added a warning so the user is warned that the NCC margin has to have a value of at least the tool diameter that is doing an iso_op job in the Tool Table
+- modified the Drillcncjob and Cncjob Tcl commands to be allowed to work without the 'dwell' and 'toolchange' arguments. If 'dwelltime' argument is present it will be assumed that the 'dwell' is True and the same for 'toolchangez' parameter, if present then 'toolchange' will be assumed to be True, else False
+- modified the extracut and multidepth parameters in Cncjob Tcl command like for dwell and toolchange
+- added ability for Tcl commands to have optional arguments with None value (meaning missing value). This case should be treated for each Tcl command in execute() method
+- fixed the Drillcncjob Tcl command by adding an custom self.options key "Tools_in_use" and build it's value, in case it does not exist, to make the toolchange command work
+- middle mouse click on closable tabs will close them
+
 30.12.2019
 
 - Buffer sub-tool in Transform Tool: added the possibility to apply a factor effectively scaling the aperture size thus the copper features sizes
 - in Transform Tool adjusted the GUI
-- fixed some decimals issues in NCC Tool, Paint Tool and Excellon Editor (they were still using the harcoded values)
+- fixed some decimals issues in NCC Tool, Paint Tool and Excellon Editor (they were still using the hardcoded values)
 - some small updates in the NCC Tool
 - changes in the Preferences UI for NCC and Paint Tool in Tool Dia entry field
 - fixed Tcl commands that use the overlap parameter to switch from fraction to percentage
-- in Transform Tool mae sure that the buffer sub-tool parameters are better explained in tooltips
+- in Transform Tool made sure that the buffer sub-tool parameters are better explained in tooltips
 - attempt to make TclCommand quit_flatcam work under Linux
 - some fixes in the NCC Tcl command (using the bool() method on some params)
 - another attempt to make TclCommand quit_flatcam work under Linux
+- another attempt to make TclCommand quit_flatcam work under Linux - use signal to call a hard exit when in Linux
+- TclCommand quit_flatcam work under Linux
 
 29.12.2019
 
@@ -28,8 +48,8 @@ CAD program, and create G-Code for Isolation routing.
 - the Gerber UI is built only once now so the process is lighter on CPU
 - the Gerber apertures marking shapes storage is now built only once because the more are built the more sluggish is the interface
 - added a new function called by shortcut key combo CTRL+G when the current widget in Plot Area is an Code Editor. It will jump to the specified line in the text.
-- fixed a small where the app tried to hide a label that I've removed previously
-- in Paint Tool Preferences allowed to add a list of initial tools separated by comma
+- fixed a small bug where the app tried to hide a label that I've removed previously
+- in Paint Tool Preferences is allowed to add a list of initial tools separated by comma
 - in Geometry Paint Tool fixed the Overlap rate to work between 0 and 99.9999%
 
 28.12.2019

+ 21 - 6
camlib.py

@@ -2415,11 +2415,8 @@ class CNCjob(Geometry):
             must_visit.remove(nearest)
         return path
 
-    def generate_from_excellon_by_tool(
-            self, exobj, tools="all", drillz = 3.0,
-            toolchange=False, toolchangez=0.1, toolchangexy='',
-            endz=2.0, startz=None,
-            excellon_optimization_type='B'):
+    def generate_from_excellon_by_tool(self, exobj, tools="all", drillz = 3.0, toolchange=False, toolchangez=0.1,
+                                       toolchangexy='', endz=2.0, startz=None, excellon_optimization_type='B'):
         """
         Creates gcode for this object from an Excellon object
         for the specified tools.
@@ -2515,6 +2512,16 @@ class CNCjob(Geometry):
             tools = [i for i, j in sorted_tools for k in selected_tools if i == k]
             log.debug("Tools selected and sorted are: %s" % str(tools))
 
+        # build a self.options['Tools_in_use'] list from scratch if we don't have one like in the case of
+        # running this method from a Tcl Command
+        build_tools_in_use_list = False
+        if 'Tools_in_use' not in self.options:
+            self.options['Tools_in_use'] = list()
+
+        # if the list is empty (either we just added the key or it was already there but empty) signal to build it
+        if not self.options['Tools_in_use']:
+            build_tools_in_use_list = True
+
         # fill the data into the self.exc_cnc_tools dictionary
         for it in sorted_tools:
             for to_ol in tools:
@@ -2550,9 +2557,17 @@ class CNCjob(Geometry):
                     self.exc_cnc_tools[it[1]]['nr_slots'] = slot_no
                     self.exc_cnc_tools[it[1]]['offset_z'] = z_off
                     self.exc_cnc_tools[it[1]]['data'] = default_data
-
                     self.exc_cnc_tools[it[1]]['solid_geometry'] = deepcopy(sol_geo)
 
+                    # build a self.options['Tools_in_use'] list from scratch if we don't have one like in the case of
+                    # running this method from a Tcl Command
+                    if build_tools_in_use_list is True:
+                        self.options['Tools_in_use'].append(
+                            [it[0], it[1], drill_no, slot_no]
+                        )
+
+        print(self.options['Tools_in_use'])
+
         self.app.inform.emit(_("Creating a list of points to drill..."))
         # Points (Group by tool)
         points = dict()

+ 47 - 0
flatcamGUI/FlatCAMGUI.py

@@ -18,6 +18,9 @@ from matplotlib.backend_bases import KeyEvent as mpl_key_event
 import webbrowser
 from copy import deepcopy
 from datetime import datetime
+
+import subprocess
+import os
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -2340,6 +2343,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.lock_toolbar(lock=lock_state)
         self.lock_action.triggered[bool].connect(self.lock_toolbar)
 
+        self.pref_open_button.clicked.connect(self.on_preferences_open_folder)
+        self.clear_btn.clicked.connect(self.on_gui_clear)
+
         # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
         # %%%%%%%%%%%%%%%%% GUI Building FINISHED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
         # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -2360,6 +2366,47 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         return False
 
+    def on_preferences_open_folder(self):
+        """
+        Will open an Explorer window set to the folder path where the FlatCAM preferences files are usually saved.
+
+        :return: None
+        """
+
+        if sys.platform == 'win32':
+            subprocess.Popen('explorer %s' % self.app.data_path)
+        elif sys.platform == 'darwin':
+            os.system('open "%s"' % self.app.data_path)
+        else:
+            subprocess.Popen(['xdg-open', self.app.data_path])
+        self.app.inform.emit('[success] %s' % _("FlatCAM Preferences Folder opened."))
+
+    def on_gui_clear(self):
+        theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
+        theme_settings.setValue('theme', 'white')
+
+        del theme_settings
+
+        resource_loc = self.app.resource_location
+
+        msgbox = QtWidgets.QMessageBox()
+        msgbox.setText(_("Are you sure you want to delete the GUI Settings? \n"))
+        msgbox.setWindowTitle(_("Clear GUI Settings"))
+        msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/trash32.png'))
+        bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
+        bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
+
+        msgbox.setDefaultButton(bt_no)
+        msgbox.exec_()
+        response = msgbox.clickedButton()
+
+        if response == bt_yes:
+            settings = QSettings("Open Source", "FlatCAM")
+            for key in settings.allKeys():
+                settings.remove(key)
+            # This will write the setting to the platform specific storage.
+            del settings
+
     def populate_toolbars(self):
         """
         Will populate the App Toolbars with theie actions

+ 33 - 1
flatcamGUI/GUIElements.py

@@ -794,12 +794,17 @@ class FCTextAreaExtended(QtWidgets.QTextEdit):
         self.completer.setModel(self.model)
         self.set_model_data(keyword_list=[])
         self.completer.insertText.connect(self.insertCompletion)
+        self.completer.popup().clicked.connect(self.insert_completion_click)
 
         self.completer_enable = False
 
     def set_model_data(self, keyword_list):
         self.model.setStringList(keyword_list)
 
+    def insert_completion_click(self):
+        self.completer.insertText.emit(self.completer.getSelected())
+        self.completer.setCompletionMode(QCompleter.PopupCompletion)
+
     def insertCompletion(self, completion):
         tc = self.textCursor()
         extra = (len(completion) - len(self.completer.completionPrefix()))
@@ -958,6 +963,7 @@ class FCPlainTextAreaExtended(QtWidgets.QPlainTextEdit):
         self.completer.setModel(self.model)
         self.set_model_data(keyword_list=[])
         self.completer.insertText.connect(self.insertCompletion)
+        self.completer.popup().clicked.connect(self.insert_completion_click)
 
         self.completer_enable = False
 
@@ -972,6 +978,10 @@ class FCPlainTextAreaExtended(QtWidgets.QPlainTextEdit):
     def set_model_data(self, keyword_list):
         self.model.setStringList(keyword_list)
 
+    def insert_completion_click(self):
+        self.completer.insertText.emit(self.completer.getSelected())
+        self.completer.setCompletionMode(QCompleter.PopupCompletion)
+
     def insertCompletion(self, completion):
         tc = self.textCursor()
         extra = (len(completion) - len(self.completer.completionPrefix()))
@@ -1263,6 +1273,8 @@ class FCDetachableTab(QtWidgets.QTabWidget):
 
         self.tabBar = self.FCTabBar(self)
         self.tabBar.onMoveTabSignal.connect(self.moveTab)
+        self.tabBar.onCloseTabSignal.connect(self.on_closetab_middle_button)
+
         self.tabBar.detachedTabDropSignal.connect(self.detachedTabDrop)
         self.set_detachable(val=True)
 
@@ -1351,6 +1363,17 @@ class FCDetachableTab(QtWidgets.QTabWidget):
 
         self.removeTab(currentIndex)
 
+    def on_closetab_middle_button(self, current_index):
+        """
+
+        :param current_index:
+        :return:
+        """
+
+        # if tab is protected don't delete it
+        if self.tabBar.tabButton(current_index, QtWidgets.QTabBar.RightSide) is not None:
+            self.removeTab(current_index)
+
     def protectTab(self, currentIndex):
         # self.FCTabBar().setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
         self.tabBar.setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
@@ -1664,7 +1687,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
         onDetachTabSignal = QtCore.pyqtSignal(int, QtCore.QPoint)
         onMoveTabSignal = QtCore.pyqtSignal(int, int)
         detachedTabDropSignal = QtCore.pyqtSignal(str, int, QtCore.QPoint)
-
+        onCloseTabSignal = QtCore.pyqtSignal(int)
         right_click = QtCore.pyqtSignal(int)
 
         def __init__(self, parent=None):
@@ -1724,6 +1747,10 @@ class FCDetachableTab(QtWidgets.QTabWidget):
             """
             if event.button() == QtCore.Qt.RightButton and self.prev_index == self.tabAt(event.pos()):
                 self.right_click.emit(self.prev_index)
+
+            if event.button() == QtCore.Qt.MiddleButton:
+                self.onCloseTabSignal.emit(int(self.tabAt(event.pos())))
+
             self.prev_index = -1
 
             QtWidgets.QTabBar.mouseReleaseEvent(self, event)
@@ -2353,10 +2380,15 @@ class _ExpandableTextEdit(QTextEdit):
         self.completer.setModel(self.model)
         self.set_model_data(keyword_list=[])
         self.completer.insertText.connect(self.insertCompletion)
+        self.completer.popup().clicked.connect(self.insert_completion_click)
 
     def set_model_data(self, keyword_list):
         self.model.setStringList(keyword_list)
 
+    def insert_completion_click(self):
+        self.completer.insertText.emit(self.completer.getSelected())
+        self.completer.setCompletionMode(QCompleter.PopupCompletion)
+
     def insertCompletion(self, completion):
         tc = self.textCursor()
         extra = (len(completion) - len(self.completer.completionPrefix()))

+ 3 - 3
flatcamGUI/PreferencesUI.py

@@ -2081,7 +2081,7 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
         )
         grid0.addWidget(milling_type_label, 4, 0)
         self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conv.'), 'value': 'cv'}])
+                                            {'label': _('Conventional'), 'value': 'cv'}])
         grid0.addWidget(self.milling_type_radio, 4, 1)
 
         # Combine passes
@@ -4379,7 +4379,7 @@ class GeometryEditorPrefGroupUI(OptionsGroupUI):
               "- conventional / useful when there is no backlash compensation")
         )
         self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conv.'), 'value': 'cv'}])
+                                            {'label': _('Conventional'), 'value': 'cv'}])
         grid0.addWidget(milling_type_label, 1, 0)
         grid0.addWidget(self.milling_type_radio, 1, 1)
 
@@ -5123,7 +5123,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         )
 
         self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conv.'), 'value': 'cv'}])
+                                            {'label': _('Conventional'), 'value': 'cv'}])
         self.milling_type_radio.setToolTip(
             _("Milling type when the selected tool is of type: 'iso_op':\n"
               "- climb / best for precision milling and to reduce tool usage\n"

+ 444 - 118
flatcamTools/ToolNonCopperClear.py

@@ -7,7 +7,7 @@
 
 from PyQt5 import QtWidgets, QtCore, QtGui
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, FCInputDialog
+from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, FCInputDialog, FCButton
 from flatcamParsers.ParseGerber import Gerber
 from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber
 import FlatCAMApp
@@ -102,8 +102,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
         form_layout.addRow(self.object_label, self.object_combo)
 
-        e_lab_0 = QtWidgets.QLabel('')
-        form_layout.addRow(e_lab_0)
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.tools_box.addWidget(separator_line)
 
         # ### Tools ## ##
         self.tools_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Tools Table'))
@@ -167,7 +169,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         )
 
         self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conv.'), 'value': 'cv'}])
+                                            {'label': _('Conventional'), 'value': 'cv'}])
         self.milling_type_radio.setToolTip(
             _("Milling type when the selected tool is of type: 'iso_op':\n"
               "- climb / best for precision milling and to reduce tool usage\n"
@@ -178,7 +180,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         grid1.addWidget(self.milling_type_radio, 0, 1)
 
         # Tool order
-        self.ncc_order_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tool order'))
+        self.ncc_order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
         self.ncc_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
                                           "'No' --> means that the used order is the one in the tool table\n"
                                           "'Forward' --> means that the tools will be ordered from small to big\n"
@@ -198,7 +200,11 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
         grid1.addWidget(self.ncc_order_label, 1, 0)
         grid1.addWidget(self.ncc_order_radio, 1, 1)
-        grid1.addWidget(QtWidgets.QLabel(''), 2, 0)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid1.addWidget(separator_line, 2, 0, 1, 2)
 
         self.milling_type_label.hide()
         self.milling_type_radio.hide()
@@ -206,8 +212,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
         # #############################################################
         # ############### Tool selection ##############################
         # #############################################################
+
+        self.grid3 = QtWidgets.QGridLayout()
+        self.tools_box.addLayout(self.grid3)
+        self.grid3.setColumnStretch(0, 0)
+        self.grid3.setColumnStretch(1, 1)
+
         self.tool_sel_label = QtWidgets.QLabel('<b>%s</b>' % _("Tool Selection"))
-        grid1.addWidget(self.tool_sel_label, 3, 0, 1, 2)
+        self.grid3.addWidget(self.tool_sel_label, 1, 0, 1, 2)
 
         # Tool Type Radio Button
         self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
@@ -224,8 +236,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "- 'V-shape'\n"
               "- Circular")
         )
-        grid1.addWidget(self.tool_type_label, 4, 0)
-        grid1.addWidget(self.tool_type_radio, 4, 1)
+        self.grid3.addWidget(self.tool_type_label, 2, 0)
+        self.grid3.addWidget(self.tool_type_radio, 2, 1)
 
         # Tip Dia
         self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
@@ -235,8 +247,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.tipdia_entry.set_precision(self.decimals)
         self.tipdia_entry.setSingleStep(0.1)
 
-        grid1.addWidget(self.tipdialabel, 5, 0)
-        grid1.addWidget(self.tipdia_entry, 5, 1)
+        self.grid3.addWidget(self.tipdialabel, 3, 0)
+        self.grid3.addWidget(self.tipdia_entry, 3, 1)
 
         # Tip Angle
         self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
@@ -247,8 +259,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.tipangle_entry.set_precision(self.decimals)
         self.tipangle_entry.setSingleStep(5)
 
-        grid1.addWidget(self.tipanglelabel, 6, 0)
-        grid1.addWidget(self.tipangle_entry, 6, 1)
+        self.grid3.addWidget(self.tipanglelabel, 4, 0)
+        self.grid3.addWidget(self.tipangle_entry, 4, 1)
 
         # Cut Z entry
         cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
@@ -264,8 +276,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
            _("Depth of cut into material. Negative value.\n"
              "In FlatCAM units.")
         )
-        grid1.addWidget(cutzlabel, 7, 0)
-        grid1.addWidget(self.cutz_entry, 7, 1)
+        self.grid3.addWidget(cutzlabel, 5, 0)
+        self.grid3.addWidget(self.cutz_entry, 5, 1)
 
         # ### Tool Diameter ####
         self.addtool_entry_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('Tool Dia'))
@@ -277,11 +289,17 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.addtool_entry = FCDoubleSpinner()
         self.addtool_entry.set_precision(self.decimals)
 
-        grid1.addWidget(self.addtool_entry_lbl, 8, 0)
-        grid1.addWidget(self.addtool_entry, 8, 1)
+        self.grid3.addWidget(self.addtool_entry_lbl, 6, 0)
+        self.grid3.addWidget(self.addtool_entry, 6, 1)
 
-        grid2 = QtWidgets.QGridLayout()
-        self.tools_box.addLayout(grid2)
+        self.addtool_from_db_btn = QtWidgets.QPushButton(_('Add Tool from DataBase'))
+        self.addtool_from_db_btn.setToolTip(
+            _("Add a new tool to the Tool Table\n"
+              "from the Tool DataBase.")
+        )
+        self.grid3.addWidget(self.addtool_from_db_btn, 7, 0, 1, 2)
+
+        hlay = QtWidgets.QHBoxLayout()
 
         self.addtool_btn = QtWidgets.QPushButton(_('Add'))
         self.addtool_btn.setToolTip(
@@ -289,31 +307,33 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "with the diameter specified above.")
         )
 
-        # self.copytool_btn = QtWidgets.QPushButton('Copy')
-        # self.copytool_btn.setToolTip(
-        #     "Copy a selection of tools in the Tool Table\n"
-        #     "by first selecting a row in the Tool Table."
-        # )
-
         self.deltool_btn = QtWidgets.QPushButton(_('Delete'))
         self.deltool_btn.setToolTip(
             _("Delete a selection of tools in the Tool Table\n"
               "by first selecting a row(s) in the Tool Table.")
         )
 
-        grid2.addWidget(self.addtool_btn, 0, 0)
-        grid2.addWidget(self.deltool_btn, 0, 2)
+        hlay.addWidget(self.addtool_btn)
+        hlay.addWidget(self.deltool_btn)
 
-        self.empty_label_0 = QtWidgets.QLabel('')
-        self.tools_box.addWidget(self.empty_label_0)
+        self.grid3.addLayout(hlay, 8, 0, 1, 2)
 
-        grid3 = QtWidgets.QGridLayout()
-        self.tools_box.addLayout(grid3)
-        grid3.setColumnStretch(0, 0)
-        grid3.setColumnStretch(1, 1)
+        self.grid3.addWidget(QtWidgets.QLabel(''), 9, 0, 1, 2)
 
-        e_lab_1 = QtWidgets.QLabel('<b>%s:</b>' % _("Parameters"))
-        grid3.addWidget(e_lab_1, 0, 0)
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.grid3.addWidget(separator_line, 10, 0, 1, 2)
+
+        self.tool_data_label = QtWidgets.QLabel(
+            "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), int(1)))
+        self.tool_data_label.setToolTip(
+            _(
+                "The data used for creating GCode.\n"
+                "Each tool store it's own set of such data."
+            )
+        )
+        self.grid3.addWidget(self.tool_data_label, 11, 0, 1, 2)
 
         # Overlap Entry
         nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate'))
@@ -331,18 +351,19 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.ncc_overlap_entry.setWrapping(True)
         self.ncc_overlap_entry.setRange(0.000, 99.9999)
         self.ncc_overlap_entry.setSingleStep(0.1)
-        grid3.addWidget(nccoverlabel, 2, 0)
-        grid3.addWidget(self.ncc_overlap_entry, 2, 1)
+
+        self.grid3.addWidget(nccoverlabel, 12, 0)
+        self.grid3.addWidget(self.ncc_overlap_entry, 12, 1)
 
         nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
         nccmarginlabel.setToolTip(
             _("Bounding box margin.")
         )
-        grid3.addWidget(nccmarginlabel, 3, 0)
         self.ncc_margin_entry = FCDoubleSpinner()
         self.ncc_margin_entry.set_precision(self.decimals)
 
-        grid3.addWidget(self.ncc_margin_entry, 3, 1)
+        self.grid3.addWidget(nccmarginlabel, 13, 0)
+        self.grid3.addWidget(self.ncc_margin_entry, 13, 1)
 
         # Method
         methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
@@ -352,13 +373,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "<B>Seed-based</B>: Outwards from seed.<BR>"
               "<B>Line-based</B>: Parallel lines.")
         )
-        grid3.addWidget(methodlabel, 4, 0)
         self.ncc_method_radio = RadioSet([
             {"label": _("Standard"), "value": "standard"},
             {"label": _("Seed-based"), "value": "seed"},
             {"label": _("Straight lines"), "value": "lines"}
         ], orientation='vertical', stretch=False)
-        grid3.addWidget(self.ncc_method_radio, 4, 1)
+
+        self.grid3.addWidget(methodlabel, 14, 0)
+        self.grid3.addWidget(self.ncc_method_radio, 14, 1)
 
         # Connect lines
         self.ncc_connect_cb = FCCheckBox('%s' % _("Connect"))
@@ -366,14 +388,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
             _("Draw lines between resulting\n"
               "segments to minimize tool lifts.")
         )
-        grid3.addWidget(self.ncc_connect_cb, 5, 0, 1, 2)
+        self.grid3.addWidget(self.ncc_connect_cb, 15, 0, 1, 2)
 
         self.ncc_contour_cb = FCCheckBox('%s' % _("Contour"))
         self.ncc_contour_cb.setToolTip(
             _("Cut around the perimeter of the polygon\n"
               "to trim rough edges.")
         )
-        grid3.addWidget(self.ncc_contour_cb, 6, 0, 1, 2)
+        self.grid3.addWidget(self.ncc_contour_cb, 16, 0, 1, 2)
 
         # Rest Machining
         self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining"))
@@ -387,7 +409,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "If not checked, use the standard algorithm.")
         )
 
-        grid3.addWidget(self.ncc_rest_cb, 7, 0, 1, 2)
+        self.grid3.addWidget(self.ncc_rest_cb, 17, 0, 1, 2)
 
         # ## NCC Offset choice
         self.ncc_choice_offset_cb = FCCheckBox('%s' % _("Offset"))
@@ -397,7 +419,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "from the copper features.\n"
               "The value can be between 0 and 10 FlatCAM units.")
         )
-        grid3.addWidget(self.ncc_choice_offset_cb, 8, 0, 1, 2)
+        self.grid3.addWidget(self.ncc_choice_offset_cb, 18, 0, 1, 2)
 
         # ## NCC Offset value
         self.ncc_offset_label = QtWidgets.QLabel('%s:' % _("Offset value"))
@@ -407,7 +429,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "from the copper features.\n"
               "The value can be between 0 and 10 FlatCAM units.")
         )
-        grid3.addWidget(self.ncc_offset_label, 9, 0)
         self.ncc_offset_spinner = FCDoubleSpinner()
         self.ncc_offset_spinner.set_range(0.00, 10.00)
         self.ncc_offset_spinner.set_precision(4)
@@ -419,7 +440,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
         else:
             self.ncc_offset_spinner.setSingleStep(0.01)
 
-        grid3.addWidget(self.ncc_offset_spinner, 9, 1)
+        self.grid3.addWidget(self.ncc_offset_label, 19, 0)
+        self.grid3.addWidget(self.ncc_offset_spinner, 19, 1)
 
         self.ncc_offset_label.hide()
         self.ncc_offset_spinner.hide()
@@ -436,11 +458,11 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
               "- 'Reference Object' - will do non copper clearing within the area specified by another object.")
         )
-        grid3.addWidget(self.reference_label, 10, 0)
-        grid3.addWidget(self.reference_radio, 10, 1)
+        self.grid3.addWidget(self.reference_label, 20, 0)
+        self.grid3.addWidget(self.reference_radio, 20, 1)
 
         form1 = QtWidgets.QFormLayout()
-        self.tools_box.addLayout(form1)
+        self.grid3.addLayout(form1, 21, 0, 1, 2)
 
         self.box_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
         self.box_combo_type_label.setToolTip(
@@ -468,6 +490,18 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.box_combo_type.hide()
         self.box_combo_type_label.hide()
 
+        separator_line2 = QtWidgets.QFrame()
+        separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.grid3.addWidget(separator_line2, 22, 0, 1, 2)
+
+        self.apply_param_to_all = FCButton(_("Apply parameters to all tools"))
+        self.apply_param_to_all.setToolTip(
+            _("The parameters in the current form will be applied\n"
+              "on all the tools from the Tool Table.")
+        )
+        self.grid3.addWidget(self.apply_param_to_all, 23, 0, 1, 2)
+
         self.generate_ncc_button = QtWidgets.QPushButton(_('Generate Geometry'))
         self.generate_ncc_button.setToolTip(
             _("Create the Geometry Object\n"
@@ -573,11 +607,265 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
         self.reset_button.clicked.connect(self.set_tool_ui)
 
+        self.tools_table.currentItemChanged.connect(self.on_row_selection_change)
+
     def on_type_obj_index_changed(self, index):
         obj_type = self.type_obj_combo.currentIndex()
         self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.object_combo.setCurrentIndex(0)
 
+    def on_row_selection_change(self):
+        self.update_ui()
+
+    def update_ui(self, row=None):
+        self.ui_disconnect()
+
+        if row is None:
+            try:
+                current_row = self.tools_table.currentRow()
+            except Exception:
+                current_row = 0
+        else:
+            current_row = row
+
+        if current_row < 0:
+            current_row = 0
+
+        # populate the form with the data from the tool associated with the row parameter
+        try:
+            item = self.tools_table.item(current_row, 3)
+            if type(item) is not None:
+                tooluid = int(item.text())
+            else:
+                return
+        except Exception as e:
+            log.debug("Tool missing. Add a tool in the Tool Table. %s" % str(e))
+            return
+
+        # update the QLabel that shows for which Tool we have the parameters in the UI form
+        self.tool_data_label.setText(
+            "<b>%s: <font color='#0000FF'>%s %d</font></b>" % (_('Parameters for'), _("Tool"), tooluid)
+        )
+
+        try:
+            # set the form with data from the newly selected tool
+            for tooluid_key, tooluid_value in list(self.ncc_tools.items()):
+                if int(tooluid_key) == tooluid:
+                    for key, value in tooluid_value.items():
+                        if key == 'data':
+                            form_value_storage = tooluid_value[key]
+                            self.update_form(form_value_storage)
+                        if key == 'offset_value':
+                            # update the offset value in the entry even if the entry is hidden
+                            self.ncc_offset_spinner.set_value(tooluid_value[key])
+
+                        if key == 'tool_type' and value == 'V':
+                            self.update_cutz()
+        except Exception as e:
+            log.debug("FlatCAMObj ---> update_ui() " + str(e))
+        self.ui_connect()
+
+    def update_cutz(self):
+        vdia = float(self.tipdia_entry.get_value())
+        half_vangle = float(self.tipangle_entry.get_value()) / 2
+
+        row = self.tools_table.currentRow()
+        tool_uid_item = self.tools_table.item(row, 3)
+        if tool_uid_item is None:
+            return
+        tool_uid = int(tool_uid_item.text())
+
+        tool_dia_item = self.tools_table.item(row, 1)
+        if tool_dia_item is None:
+            return
+        tooldia = float(tool_dia_item.text())
+
+        new_cutz = (tooldia - vdia) / (2 * math.tan(math.radians(half_vangle)))
+        new_cutz = float('%.*f' % (self.decimals, new_cutz)) * -1.0   # this value has to be negative
+
+        self.cutz_entry.set_value(new_cutz)
+
+        # store the new CutZ value into storage (self.ncc_tools)
+        for tooluid_key, tooluid_value in self.ncc_tools.items():
+            if int(tooluid_key) == tool_uid:
+                tooluid_value['data']['cutz'] = new_cutz
+
+    def on_tooltable_cellwidget_change(self):
+        cw = self.sender()
+        cw_index = self.tools_table.indexAt(cw.pos())
+        cw_row = cw_index.row()
+        cw_col = cw_index.column()
+        current_uid = int(self.tools_table.item(cw_row, 3).text())
+
+        # store the text of the cellWidget that changed it's index in the self.tools
+        for tooluid_key, tooluid_value in self.ncc_tools.items():
+            if int(tooluid_key) == current_uid:
+                cb_txt = cw.currentText()
+                if cw_col == 2:
+                    tooluid_value['tool_type'] = cb_txt
+
+    def update_form(self, dict_storage):
+        for form_key in self.form_fields:
+            for storage_key in dict_storage:
+                if form_key == storage_key:
+                    try:
+                        self.form_fields[form_key].set_value(dict_storage[form_key])
+                    except Exception as e:
+                        log.debug(str(e))
+
+        # this is done here because those buttons control through OptionalInputSelection if some entry's are Enabled
+        # or not. But due of using the ui_disconnect() status is no longer updated and I had to do it here
+        self.ui.ois_dwell_geo.on_cb_change()
+        self.ui.ois_mpass_geo.on_cb_change()
+        self.ui.ois_tcz_geo.on_cb_change()
+
+    def on_apply_param_to_all_clicked(self):
+        if self.tools_table.rowCount() == 0:
+            # there is no tool in tool table so we can't save the GUI elements values to storage
+            log.debug("NonCopperClear.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.")
+            return
+
+        self.ui_disconnect()
+
+        row = self.tools_table.currentRow()
+        if row < 0:
+            row = 0
+
+        # store all the data associated with the row parameter to the self.tools storage
+        tooldia_item = float(self.tools_table.item(row, 1).text())
+        type_item = self.tools_table.cellWidget(row, 2).currentText()
+        operation_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText()
+
+        offset_item = self.ncc_choice_offset_cb.get_value()
+        offset_value_item = float(self.ncc_offset_spinner.get_value())
+
+        # this new dict will hold the actual useful data, another dict that is the value of key 'data'
+        temp_tools = {}
+        temp_dia = {}
+        temp_data = {}
+
+        for tooluid_key, tooluid_value in self.ncc_tools.items():
+            for key, value in tooluid_value.items():
+                if key == 'tooldia':
+                    temp_dia[key] = tooldia_item
+                # update the 'offset', 'type' and 'tool_type' sections
+                if key == 'offset':
+                    temp_dia[key] = offset_item
+                if key == 'type':
+                    temp_dia[key] = type_item
+                if key == 'offset_value':
+                    temp_dia[key] = offset_value_item
+
+                if key == 'data':
+                    # update the 'data' section
+                    for data_key in tooluid_value[key].keys():
+                        for form_key, form_value in self.form_fields.items():
+                            if form_key == data_key:
+                                temp_data[data_key] = form_value.get_value()
+                        # make sure we make a copy of the keys not in the form (we may use 'data' keys that are
+                        # updated from self.app.defaults
+                        if data_key not in self.form_fields:
+                            temp_data[data_key] = value[data_key]
+                    temp_dia[key] = deepcopy(temp_data)
+                    temp_data.clear()
+
+                if key == 'solid_geometry':
+                    temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry'])
+
+                temp_tools[tooluid_key] = deepcopy(temp_dia)
+
+        self.ncc_tools.clear()
+        self.ncc_tools = deepcopy(temp_tools)
+        temp_tools.clear()
+
+        self.ui_connect()
+
+    def gui_form_to_storage(self):
+        if self.tools_table.rowCount() == 0:
+            # there is no tool in tool table so we can't save the GUI elements values to storage
+            log.debug("NonCopperClear.gui_form_to_storage() --> no tool in Tools Table, aborting.")
+            return
+
+        self.ui_disconnect()
+        widget_changed = self.sender()
+        try:
+            widget_idx = self.grid3.indexOf(widget_changed)
+        except Exception as e:
+            return
+
+        # those are the indexes for the V-Tip Dia and V-Tip Angle, if edited calculate the new Cut Z
+        if widget_idx == 1 or widget_idx == 3:
+            self.update_cutz()
+
+        # the original connect() function of the OptionalInputSelection is no longer working because of the
+        # ui_diconnect() so I use this 'hack'
+        # if isinstance(widget_changed, FCCheckBox):
+        #     if widget_changed.text() == 'Multi-Depth:':
+        #         self.ui.ois_mpass_geo.on_cb_change()
+        #
+        #     if widget_changed.text() == 'Tool change':
+        #         self.ui.ois_tcz_geo.on_cb_change()
+        #
+        #     if widget_changed.text() == 'Dwell:':
+        #         self.ui.ois_dwell_geo.on_cb_change()
+
+        row = self.tools_table.currentRow()
+        if row < 0:
+            row = 0
+
+        # store all the data associated with the row parameter to the self.tools storage
+        tooldia_item = float(self.tools_table.item(row, 1).text())
+        tool_type_item = self.tools_table.cellWidget(row, 2).currentText()
+        operation_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText()
+
+        offset_item = self.ncc_choice_offset_cb.get_value()
+        offset_value_item = float(self.ncc_offset_spinner.get_value())
+
+        tooluid_item = int(self.ui.geo_tools_table.item(row, 3).text())
+
+        # this new dict will hold the actual useful data, another dict that is the value of key 'data'
+        temp_tools = {}
+        temp_dia = {}
+        temp_data = {}
+
+        for tooluid_key, tooluid_value in self.tools.items():
+            if int(tooluid_key) == tooluid_item:
+                for key, value in tooluid_value.items():
+                    if key == 'tooldia':
+                        temp_dia[key] = tooldia_item
+                    # update the 'offset', 'type' and 'tool_type' sections
+                    if key == 'offset':
+                        temp_dia[key] = offset_item
+                    if key == 'tool_type':
+                        temp_dia[key] = tool_type_item
+                    if key == 'offset_value':
+                        temp_dia[key] = offset_value_item
+
+                    if key == 'data':
+                        # update the 'data' section
+                        for data_key in tooluid_value[key].keys():
+                            for form_key, form_value in self.form_fields.items():
+                                if form_key == data_key:
+                                    temp_data[data_key] = form_value.get_value()
+                            # make sure we make a copy of the keys not in the form (we may use 'data' keys that are
+                            # updated from self.app.defaults
+                            if data_key not in self.form_fields:
+                                temp_data[data_key] = value[data_key]
+                        temp_dia[key] = deepcopy(temp_data)
+                        temp_data.clear()
+
+                    if key == 'solid_geometry':
+                        temp_dia[key] = deepcopy(self.ncc_tools[tooluid_key]['solid_geometry'])
+
+                    temp_tools[tooluid_key] = deepcopy(temp_dia)
+            else:
+                temp_tools[tooluid_key] = deepcopy(tooluid_value)
+
+        self.ncc_tools.clear()
+        self.ncc_tools = deepcopy(temp_tools)
+        temp_tools.clear()
+        self.ui_connect()
+
     def on_add_tool_by_key(self):
         tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
                                        text='%s:' % _('Enter a Tool Diameter'),
@@ -1007,7 +1295,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
         if float('%.*f' % (self.decimals, tool_dia)) in tool_dias:
             if muted is None:
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _("Adding tool cancelled. Tool already in Tool Table."))
-            self.tools_table.itemChanged.connect(self.on_tool_edit)
+            # self.tools_table.itemChanged.connect(self.on_tool_edit)
+            self.ui_connect()
             return
         else:
             if muted is None:
@@ -1625,6 +1914,11 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
                 if empty == 'fail':
                     return 'fail'
+
+                if empty.is_empty:
+                    app_obj.inform.emit('[ERROR_NOTCL] %s' %
+                                        _("Could not get the extent of the area to be non copper cleared."))
+                    return 'fail'
             elif isinstance(ncc_obj, FlatCAMGerber) and isotooldia:
                 isolated_geo = []
 
@@ -1649,6 +1943,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     if isolated_geo == 'fail':
                         app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
                     else:
+                        if ncc_margin < tool_iso:
+                            app_obj.inform.emit('[WARNING_NOTCL] %s' % _("Isolation geometry is broken. Margin is less "
+                                                                         "than isolation tool diameter."))
                         try:
                             for geo_elem in isolated_geo:
                                 # provide the app with a way to process the GUI events when in a blocking loop
@@ -1722,26 +2019,28 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 if empty == 'fail':
                     return 'fail'
 
+                if empty.is_empty:
+                    app_obj.inform.emit('[ERROR_NOTCL] %s' %
+                                        _("Isolation geometry is broken. Margin is less than isolation tool diameter."))
+                    return 'fail'
+
             elif isinstance(ncc_obj, FlatCAMGeometry):
                 sol_geo = cascaded_union(ncc_obj.solid_geometry)
                 if has_offset is True:
-                    app_obj.inform.emit('[WARNING_NOTCL] %s ...' %
-                                        _("Buffering"))
+                    app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
                     sol_geo = sol_geo.buffer(distance=ncc_offset)
-                    app_obj.inform.emit('[success] %s ...' %
-                                        _("Buffering finished"))
+                    app_obj.inform.emit('[success] %s ...' %  _("Buffering finished"))
                 empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
                 if empty == 'fail':
                     return 'fail'
 
-            else:
-                app_obj.inform.emit('[ERROR_NOTCL] %s' %
-                                    _('The selected object is not suitable for copper clearing.'))
-                return
+                if empty.is_empty:
+                    app_obj.inform.emit('[ERROR_NOTCL] %s' %
+                                        _("Could not get the extent of the area to be non copper cleared."))
+                    return 'fail'
 
-            if empty.is_empty:
-                app_obj.inform.emit('[ERROR_NOTCL] %s' %
-                                    _("Could not get the extent of the area to be non copper cleared."))
+            else:
+                app_obj.inform.emit('[ERROR_NOTCL] %s' %  _('The selected object is not suitable for copper clearing.'))
                 return 'fail'
 
             if type(empty) is Polygon:
@@ -1761,11 +2060,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 # provide the app with a way to process the GUI events when in a blocking loop
                 QtWidgets.QApplication.processEvents()
 
-                app_obj.inform.emit(
-                    '[success] %s %s%s %s' % (_('NCC Tool clearing with tool diameter = '),
-                                              str(tool),
-                                              units.lower(),
-                                              _('started.'))
+                app_obj.inform.emit('[success] %s = %s%s %s' % (
+                    _('NCC Tool clearing with tool diameter'), str(tool), units.lower(), _('started.'))
                 )
                 app_obj.proc_container.update_view_text(' %d%%' % 0)
 
@@ -1835,7 +2131,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                                 poly_processed.append(False)
                                                 log.warning("Polygon in MultiPolygon can not be cleared.")
                                         else:
-                                            log.warning("Geo in Iterable can not be cleared beacuse it is not Polygon. "
+                                            log.warning("Geo in Iterable can not be cleared because it is not Polygon. "
                                                         "It is: %s" % str(type(pol)))
                                 except TypeError:
                                     if isinstance(p, Polygon):
@@ -1941,32 +2237,59 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 if geo_obj.tools[tooluid]['solid_geometry']:
                     has_solid_geo += 1
             if has_solid_geo == 0:
-                app_obj.inform.emit('[ERROR] %s' % _("There is no NCC Geometry in the file.\n"
-                                                     "Usually it means that the tool diameter is too big "
-                                                     "for the painted geometry.\n"
-                                                     "Change the painting parameters and try again."))
-                return
+                app_obj.inform.emit('[ERROR] %s' %
+                                    _("There is no NCC Geometry in the file.\n"
+                                      "Usually it means that the tool diameter is too big for the painted geometry.\n"
+                                      "Change the painting parameters and try again."))
+                return 'fail'
 
-            # create the solid_geometry
-            geo_obj.solid_geometry = list()
-            for tooluid in geo_obj.tools:
-                if geo_obj.tools[tooluid]['solid_geometry']:
-                    try:
-                        for geo in geo_obj.tools[tooluid]['solid_geometry']:
-                            geo_obj.solid_geometry.append(geo)
-                    except TypeError:
-                        geo_obj.solid_geometry.append(geo_obj.tools[tooluid]['solid_geometry'])
-
-            # Experimental...
-            # print("Indexing...", end=' ')
-            # geo_obj.make_index()
-            if warning_flag == 0:
-                self.app.inform.emit('[success] %s' % _("NCC Tool clear all done."))
+            # check to see if geo_obj.tools is empty
+            # it will be updated only if there is a solid_geometry for tools
+            if geo_obj.tools:
+                if warning_flag == 0:
+                    self.app.inform.emit('[success] %s' % _("NCC Tool clear all done."))
+                else:
+                    self.app.inform.emit('[WARNING] %s: %s %s.' % (
+                        _("NCC Tool clear all done but the copper features isolation is broken for"),
+                        str(warning_flag),
+                        _("tools")))
+                    return
+
+                # create the solid_geometry
+                geo_obj.solid_geometry = list()
+                for tooluid in geo_obj.tools:
+                    if geo_obj.tools[tooluid]['solid_geometry']:
+                        try:
+                            for geo in geo_obj.tools[tooluid]['solid_geometry']:
+                                geo_obj.solid_geometry.append(geo)
+                        except TypeError:
+                            geo_obj.solid_geometry.append(geo_obj.tools[tooluid]['solid_geometry'])
             else:
-                self.app.inform.emit('[WARNING] %s: %s %s.' % (_("NCC Tool clear all done but the copper features "
-                                                                 "isolation is broken for"),
-                                                               str(warning_flag),
-                                                               _("tools")))
+                # I will use this variable for this purpose although it was meant for something else
+                # signal that we have no geo in the object therefore don't create it
+                app_obj.poly_not_cleared = False
+                return "fail"
+
+            # create the solid_geometry
+            # geo_obj.solid_geometry = list()
+            # for tooluid in geo_obj.tools:
+            #     if geo_obj.tools[tooluid]['solid_geometry']:
+            #         try:
+            #             for geo in geo_obj.tools[tooluid]['solid_geometry']:
+            #                 geo_obj.solid_geometry.append(geo)
+            #         except TypeError:
+            #             geo_obj.solid_geometry.append(geo_obj.tools[tooluid]['solid_geometry'])
+            #
+            # # Experimental...
+            # # print("Indexing...", end=' ')
+            # # geo_obj.make_index()
+            # if warning_flag == 0:
+            #     self.app.inform.emit('[success] %s' % _("NCC Tool clear all done."))
+            # else:
+            #     self.app.inform.emit('[WARNING] %s: %s %s.' % (
+            #         _("NCC Tool clear all done but the copper features isolation is broken for"),
+            #         str(warning_flag),
+            #         _("tools")))
 
         # ###########################################################################################
         # Initializes the new geometry object for the case of the rest-machining ####################
@@ -2008,15 +2331,17 @@ class NonCopperClear(FlatCAMTool, Gerber):
             if isinstance(ncc_obj, FlatCAMGerber) and not isotooldia:
                 sol_geo = ncc_obj.solid_geometry
                 if has_offset is True:
-                    app_obj.inform.emit('[WARNING_NOTCL] %s ...' %
-                                        _("Buffering"))
+                    app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
                     sol_geo = sol_geo.buffer(distance=ncc_offset)
-                    app_obj.inform.emit('[success] %s ...' %
-                                        _("Buffering finished"))
+                    app_obj.inform.emit('[success] %s ...' %  _("Buffering finished"))
                 empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
                 if empty == 'fail':
                     return 'fail'
 
+                if empty.is_empty:
+                    app_obj.inform.emit('[ERROR_NOTCL] %s' %
+                                        _("Could not get the extent of the area to be non copper cleared."))
+                    return 'fail'
             elif isinstance(ncc_obj, FlatCAMGerber) and isotooldia:
                 isolated_geo = []
                 self.solid_geometry = ncc_obj.solid_geometry
@@ -2035,6 +2360,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     if isolated_geo == 'fail':
                         app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
                     else:
+                        app_obj.inform.emit('[WARNING_NOTCL] %s' % _("Isolation geometry is broken. Margin is less "
+                                                                     "than isolation tool diameter."))
+
                         try:
                             for geo_elem in isolated_geo:
                                 # provide the app with a way to process the GUI events when in a blocking loop
@@ -2104,36 +2432,36 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                 sol_geo = cascaded_union(isolated_geo)
                 if has_offset is True:
-                    app_obj.inform.emit('[WARNING_NOTCL] %s ...' %
-                                        _("Buffering"))
+                    app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
                     sol_geo = sol_geo.buffer(distance=ncc_offset)
-                    app_obj.inform.emit('[success] %s ...' %
-                                        _("Buffering finished"))
+                    app_obj.inform.emit('[success] %s ...' % _("Buffering finished"))
                 empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
                 if empty == 'fail':
                     return 'fail'
 
+                if empty.is_empty:
+                    app_obj.inform.emit('[ERROR_NOTCL] %s' %
+                                        _("Isolation geometry is broken. Margin is less than isolation tool diameter."))
+                    return 'fail'
+
             elif isinstance(ncc_obj, FlatCAMGeometry):
                 sol_geo = cascaded_union(ncc_obj.solid_geometry)
                 if has_offset is True:
-                    app_obj.inform.emit('[WARNING_NOTCL] %s ...' %
-                                        _("Buffering"))
+                    app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
                     sol_geo = sol_geo.buffer(distance=ncc_offset)
-                    app_obj.inform.emit('[success] %s ...' %
-                                        _("Buffering finished"))
+                    app_obj.inform.emit('[success] %s ...' % _("Buffering finished"))
                 empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
                 if empty == 'fail':
                     return 'fail'
+
+                if empty.is_empty:
+                    app_obj.inform.emit('[ERROR_NOTCL] %s' %
+                                        _("Could not get the extent of the area to be non copper cleared."))
+                    return 'fail'
             else:
-                app_obj.inform.emit('[ERROR_NOTCL] %s' %
-                                    _('The selected object is not suitable for copper clearing.'))
+                app_obj.inform.emit('[ERROR_NOTCL] %s' % _('The selected object is not suitable for copper clearing.'))
                 return
 
-            if empty.is_empty:
-                app_obj.inform.emit('[ERROR_NOTCL] %s' %
-                                    _("Could not get the extent of the area to be non copper cleared."))
-                return 'fail'
-
             if self.app.abort_flag:
                 # graceful abort requested by the user
                 raise FlatCAMApp.GracefulException
@@ -2155,11 +2483,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 tool = sorted_tools.pop(0)
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
 
-                app_obj.inform.emit(
-                    '[success] %s %s%s %s' % (_('NCC Tool clearing with tool diameter = '),
-                                              str(tool),
-                                              units.lower(),
-                                              _('started.'))
+                app_obj.inform.emit('[success] %s = %s%s %s' % (
+                    _('NCC Tool clearing with tool diameter'), str(tool), units.lower(), _('started.'))
                 )
                 app_obj.proc_container.update_view_text(' %d%%' % 0)
 
@@ -2239,7 +2564,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                 elif isinstance(p, MultiPolygon):
                                     for poly in p:
                                         if poly is not None:
-                                            # provide the app with a way to process the GUI events when in a blocking loop
+                                            # provide the app with a way to process the GUI events when
+                                            # in a blocking loop
                                             QtWidgets.QApplication.processEvents()
 
                                             try:
@@ -2336,7 +2662,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     self.app.inform.emit(
                         '[WARNING] %s: %s %s.' % (_("NCC Tool Rest Machining clear all done but the copper features "
                                                     "isolation is broken for"), str(warning_flag), _("tools")))
-                return
+                    return
 
                 # create the solid_geometry
                 geo_obj.solid_geometry = list()

+ 1 - 1
flatcamTools/ToolProperties.py

@@ -468,7 +468,7 @@ class Properties(FlatCAMTool):
                         _("Depth of Cut"),
                         '%.*f %s' % (
                             self.decimals,
-                            (obj.z_cut - obj.tool_offset[tool_dia]),
+                            (obj.z_cut - abs(obj.tool_offset[tool_dia])),
                             self.app.defaults['units'].lower()
                         )
                     ],

+ 4 - 4
flatcamTools/ToolShell.py

@@ -56,7 +56,7 @@ class TermWidget(QWidget):
         self._history = ['']  # current empty line
         self._historyIndex = 0
 
-    def open_proccessing(self, detail=None):
+    def open_processing(self, detail=None):
         """
         Open processing and disable using shell commands again until all commands are finished
 
@@ -67,14 +67,14 @@ class TermWidget(QWidget):
         self._edit.setTextColor(Qt.white)
         self._edit.setTextBackgroundColor(Qt.darkGreen)
         if detail is None:
-            self._edit.setPlainText(_("...proccessing..."))
+            self._edit.setPlainText(_("...processing..."))
         else:
-            self._edit.setPlainText('%s [%s]' % (_("...proccessing..."),  detail))
+            self._edit.setPlainText('%s [%s]' % (_("...processing..."),  detail))
 
         self._edit.setDisabled(True)
         self._edit.setFocus()
 
-    def close_proccessing(self):
+    def close_processing(self):
         """
         Close processing and enable using shell commands  again
         :return:

+ 0 - 0
share/dark_resources/makearea32.png → share/dark_resources/markarea32.png


+ 23 - 10
tclCommands/TclCommand.py

@@ -177,19 +177,26 @@ class TclCommand(object):
         arguments = []
         n = len(args)
 
-        name = None
+        option_name = None
+
         for i in range(n):
             match = re.search(r'^-([a-zA-Z].*)', args[i])
             if match:
-                assert name is None
-                name = match.group(1)
+                # assert option_name is None
+                if option_name is not None:
+                    options[option_name] = None
+
+                option_name = match.group(1)
                 continue
 
-            if name is None:
+            if option_name is None:
                 arguments.append(args[i])
             else:
-                options[name] = args[i]
-                name = None
+                options[option_name] = args[i]
+                option_name = None
+
+        if option_name is not None:
+            options[option_name] = None
 
         return arguments, options
 
@@ -211,6 +218,7 @@ class TclCommand(object):
         for argument in arguments:
             if len(self.arg_names) > idx:
                 key, arg_type = arg_names_items[idx]
+
                 try:
                     named_args[key] = arg_type(argument)
                 except Exception as e:
@@ -226,7 +234,12 @@ class TclCommand(object):
                 self.raise_tcl_error('Unknown parameter: %s' % key)
             try:
                 if key != 'timeout':
-                    named_args[key] = self.option_types[key](options[key])
+                    # None options are allowed; if None then the defaults are used
+                    # - must be implemented in the Tcl commands
+                    if options[key] is not None:
+                        named_args[key] = self.option_types[key](options[key])
+                    else:
+                        named_args[key] = options[key]
                 else:
                     named_args[key] = int(options[key])
             except Exception as e:
@@ -258,8 +271,8 @@ class TclCommand(object):
         :return: raise exception
         """
 
-        # becouse of signaling we cannot call error to TCL from here but when task
-        # is finished also nonsignaled are handled here to better exception
+        # because of signaling we cannot call error to TCL from here but when task
+        # is finished also non-signaled are handled here to better exception
         # handling and  displayed after command is finished
         raise self.app.TclErrorException(text)
 
@@ -400,7 +413,7 @@ class TclCommandSignaled(TclCommand):
                 passed_timeout = self.app.defaults['global_background_timeout']
 
             # set detail for processing, it will be there until next open or close
-            self.app.shell.open_proccessing(self.get_current_command())
+            self.app.shell.open_processing(self.get_current_command())
 
             def handle_finished(obj):
                 self.app.shell_command_finished.disconnect(handle_finished)

+ 43 - 23
tclCommands/TclCommandCncjob.py

@@ -33,17 +33,13 @@ class TclCommandCncjob(TclCommandSignaled):
         ('feedrate', float),
         ('feedrate_z', float),
         ('feedrate_rapid', float),
-        ('multidepth', bool),
-        ('extracut', bool),
         ('extracut_length', float),
         ('depthperpass', float),
-        ('toolchange', int),
         ('toolchangez', float),
         ('toolchangexy', tuple),
         ('startz', float),
         ('endz', float),
         ('spindlespeed', int),
-        ('dwell', bool),
         ('dwelltime', float),
         ('pp', str),
         ('muted', int),
@@ -64,18 +60,16 @@ class TclCommandCncjob(TclCommandSignaled):
             ('feedrate', 'Moving speed on X-Y plane when cutting.'),
             ('feedrate_z', 'Moving speed on Z plane when cutting.'),
             ('feedrate_rapid', 'Rapid moving at speed when cutting.'),
-            ('multidepth', 'Use or not multidepth cnc cut. (True or False)'),
-            ('extracut', 'Use or not an extra cnccut over the first point in path,in the job end (example: True)'),
-            ('extracut', 'The value for extra cnccut over the first point in path,in the job end; float'),
-            ('depthperpass', 'Height of one layer for multidepth.'),
-            ('toolchange', 'Enable tool changes (example: True).'),
-            ('toolchangez', 'Z distance for toolchange (example: 30.0).'),
+            ('extracut_length', 'The value for extra cnccut over the first point in path,in the job end; float'),
+            ('depthperpass', 'If present then use multidepth cnc cut. Height of one layer for multidepth.'),
+            ('toolchangez', 'Z distance for toolchange (example: 30.0).\n'
+                            'If used in the command then a toolchange event will be included in gcode'),
             ('toolchangexy', 'X, Y coordonates for toolchange in format (x, y) (example: (2.0, 3.1) ).'),
             ('startz', 'Height before the first move.'),
             ('endz', 'Height where the last move will park.'),
             ('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'),
-            ('dwell', 'True or False; use (or not) the dwell'),
-            ('dwelltime', 'Time to pause to allow the spindle to reach the full speed'),
+            ('dwelltime', 'Time to pause to allow the spindle to reach the full speed.\n'
+                          'If it is not used in command then it will not be included'),
             ('outname', 'Name of the resulting Geometry object.'),
             ('pp', 'Name of the Geometry preprocessor. No quotes, case sensitive'),
             ('muted', 'It will not put errors in the Shell.')
@@ -136,26 +130,52 @@ class TclCommandCncjob(TclCommandSignaled):
         args["feedrate_rapid"] = args["feedrate_rapid"] if "feedrate_rapid" in args and args["feedrate_rapid"] else \
             obj.options["feedrate_rapid"]
 
-        args["multidepth"] = bool(args["multidepth"]) if "multidepth" in args else obj.options["multidepth"]
-        args["extracut"] = bool(args["extracut"]) if "extracut" in args else obj.options["extracut"]
-        args["extracut_length"] = float(args["extracut_length"]) if "extracut_length" in args else \
-            obj.options["extracut_length"]
-        args["depthperpass"] = args["depthperpass"] if "depthperpass" in args and args["depthperpass"] else \
-            obj.options["depthperpass"]
+        if "extracut_length" in args:
+            args["extracut"] = True
+            if args["extracut_length"] is None:
+                args["extracut_length"] = 0.0
+            else:
+                args["extracut_length"] = float(args["extracut_length"])
+        else:
+            args["extracut"] = False
+
+        if "depthperpass" in args:
+            args["multidepth"] = True
+            if args["depthperpass"] is None:
+                args["depthperpass"] = obj.options["depthperpass"]
+            else:
+                args["depthperpass"] = float(args["depthperpass"])
+        else:
+            args["multidepth"] = False
 
         args["startz"] = args["startz"] if "startz" in args and args["startz"] else \
             self.app.defaults["geometry_startz"]
         args["endz"] = args["endz"] if "endz" in args and args["endz"] else obj.options["endz"]
 
         args["spindlespeed"] = args["spindlespeed"] if "spindlespeed" in args and args["spindlespeed"] != 0 else None
-        args["dwell"] = bool(args["dwell"]) if "dwell" in args else obj.options["dwell"]
-        args["dwelltime"] = args["dwelltime"] if "dwelltime" in args and args["dwelltime"] else obj.options["dwelltime"]
+
+        if 'dwelltime' in args:
+            args["dwell"] = True
+            if args['dwelltime'] is None:
+                args["dwelltime"] = float(obj.options["dwelltime"])
+            else:
+                args["dwelltime"] = float(args['dwelltime'])
+        else:
+            args["dwell"] = False
+            args["dwelltime"] = 0.0
 
         args["pp"] = args["pp"] if "pp" in args and args["pp"] else obj.options["ppname_g"]
 
-        args["toolchange"] = True if "toolchange" in args and args["toolchange"] == 1 else False
-        args["toolchangez"] = args["toolchangez"] if "toolchangez" in args and args["toolchangez"] else \
-            obj.options["toolchangez"]
+        if "toolchangez" in args:
+            args["toolchange"] = True
+            if args["toolchangez"] is not None:
+                args["toolchangez"] = args["toolchangez"]
+            else:
+                args["toolchangez"] = obj.options["toolchangez"]
+        else:
+            args["toolchange"] = False
+            args["toolchangez"] = 0.0
+
         args["toolchangexy"] = args["toolchangexy"] if "toolchangexy" in args and args["toolchangexy"] else \
             self.app.defaults["geometry_toolchangexy"]
 

+ 41 - 19
tclCommands/TclCommandDrillcncjob.py

@@ -26,11 +26,9 @@ class TclCommandDrillcncjob(TclCommandSignaled):
         ('feedrate', float),
         ('feedrate_rapid', float),
         ('spindlespeed', int),
-        ('toolchange', bool),
         ('toolchangez', float),
         ('toolchangexy', tuple),
         ('endz', float),
-        ('dwell', bool),
         ('dwelltime', float),
         ('pp', str),
         ('outname', str),
@@ -48,18 +46,19 @@ class TclCommandDrillcncjob(TclCommandSignaled):
         'args': collections.OrderedDict([
             ('name', 'Name of the source object.'),
             ('drilled_dias',
-             'Comma separated tool diameters of the drills to be drilled (example: 0.6, 1.0 or 3.125).'),
+             'Comma separated tool diameters of the drills to be drilled (example: 0.6,1.0 or 3.125). '
+             'No space allowed'),
             ('drillz', 'Drill depth into material (example: -2.0).'),
             ('travelz', 'Travel distance above material (example: 2.0).'),
             ('feedrate', 'Drilling feed rate.'),
             ('feedrate_rapid', 'Rapid drilling feed rate.'),
             ('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'),
-            ('toolchange', 'Enable tool changes (example: True).'),
-            ('toolchangez', 'Z distance for toolchange (example: 30.0).'),
+            ('toolchangez', 'Z distance for toolchange (example: 30.0).\n'
+                            'If used in the command then a toolchange event will be included in gcode'),
             ('toolchangexy', 'X, Y coordonates for toolchange in format (x, y) (example: (2.0, 3.1) ).'),
             ('endz', 'Z distance at job end (example: 30.0).'),
-            ('dwell', 'True or False; use (or not) the dwell'),
-            ('dwelltime', 'Time to pause to allow the spindle to reach the full speed'),
+            ('dwelltime', 'Time to pause to allow the spindle to reach the full speed.\n'
+                          'If it is not used in command then it will not be included'),
             ('pp', 'This is the Excellon preprocessor name: case_sensitive, no_quotes'),
             ('outname', 'Name of the resulting Geometry object.'),
             ('opt_type', 'Name of move optimization type. B by default for Basic OR-Tools, M for Metaheuristic OR-Tools'
@@ -73,8 +72,8 @@ class TclCommandDrillcncjob(TclCommandSignaled):
             ('muted', 'It will not put errors in the Shell or status bar.')
         ]),
         'examples': ['drillcncjob test.TXT -drillz -1.5 -travelz 14 -feedrate 222 -feedrate_rapid 456 -spindlespeed 777'
-                     ' -toolchange True -toolchangez 33 -endz 22 -pp default\n'
-                     'Usage of -feedrate_rapid matter only when the posptocessor is using it, like -marlin-.']
+                     ' -toolchangez 33 -endz 22 -pp default\n'
+                     'Usage of -feedrate_rapid matter only when the preprocessor is using it, like -marlin-.']
     }
 
     def execute(self, args, unnamed_args):
@@ -118,7 +117,6 @@ class TclCommandDrillcncjob(TclCommandSignaled):
 
         def job_init(job_obj, app_obj):
             # tools = args["tools"] if "tools" in args else 'all'
-            units = self.app.defaults['units'].upper()
 
             try:
                 if 'drilled_dias' in args and args['drilled_dias'] != 'all':
@@ -171,11 +169,19 @@ class TclCommandDrillcncjob(TclCommandSignaled):
                 else:
                     return "fail"
 
-            drillz = args["drillz"] if "drillz" in args and args["drillz"] else obj.options["drillz"]
-            toolchangez = args["toolchangez"] if "toolchangez" in args and args["toolchangez"] else \
-                obj.options["toolchangez"]
-            endz = args["endz"] if "endz" in args and args["endz"] else obj.options["endz"]
-            toolchange = True if "toolchange" in args and bool(args["toolchange"]) is True else False
+            drillz = args["drillz"] if "drillz" in args and args["drillz"] is not None else obj.options["drillz"]
+
+            if "toolchangez" in args:
+                toolchange = True
+                if args["toolchangez"] is not None:
+                    toolchangez = args["toolchangez"]
+                else:
+                    toolchangez = obj.options["toolchangez"]
+            else:
+                toolchange = False
+                toolchangez = 0.0
+
+            endz = args["endz"] if "endz" in args and args["endz"] is not None else obj.options["endz"]
             opt_type = args["opt_type"] if "opt_type" in args and args["opt_type"] else 'B'
 
             job_obj.z_move = args["travelz"] if "travelz" in args and args["travelz"] else obj.options["travelz"]
@@ -183,9 +189,15 @@ class TclCommandDrillcncjob(TclCommandSignaled):
             job_obj.feedrate_rapid = args["feedrate_rapid"] \
                 if "feedrate_rapid" in args and args["feedrate_rapid"] else obj.options["feedrate_rapid"]
 
-            if bool(args['dwell']) and args['dwelltime']:
+            if 'dwelltime' in args:
                 job_obj.dwell = True
-                job_obj.dwelltime = float(args['dwelltime'])
+                if args['dwelltime'] is not None:
+                    job_obj.dwelltime = float(args['dwelltime'])
+                else:
+                    job_obj.dwelltime = float(obj.options["dwelltime"])
+            else:
+                job_obj.dwell = False
+                job_obj.dwelltime = 0.0
 
             job_obj.spindlespeed = args["spindlespeed"] if "spindlespeed" in args else None
             job_obj.pp_excellon_name = args["pp"] if "pp" in args and args["pp"] \
@@ -206,9 +218,19 @@ class TclCommandDrillcncjob(TclCommandSignaled):
             job_obj.options['xmax'] = xmax
             job_obj.options['ymax'] = ymax
 
-            job_obj.generate_from_excellon_by_tool(obj, tools, drillz=drillz, toolchangez=toolchangez,
-                                                   endz=endz,
+            job_obj.generate_from_excellon_by_tool(obj, tools, drillz=drillz, toolchangez=toolchangez, endz=endz,
                                                    toolchange=toolchange, excellon_optimization_type=opt_type)
+
+            for t_item in job_obj.exc_cnc_tools:
+                job_obj.exc_cnc_tools[t_item]['data']['offset'] = \
+                    float(job_obj.exc_cnc_tools[t_item]['offset_z']) + float(drillz)
+                job_obj.exc_cnc_tools[t_item]['data']['ppname_e'] = obj.options['ppname_e']
+
+                # for now there is no tool offset support in this Tcl Command so we write the 0.0 value here
+                job_obj.tool_offset[t_item] = 0.0
+
+            job_obj.origin_kind = 'excellon'
+
             job_obj.gcode_parse()
             job_obj.create_geometry()