Browse Source

Merged in marius_stanciu/flatcam_beta/Beta (pull request #210)

Beta - Bug fixes mostly
Marius Stanciu 6 years ago
parent
commit
fd0d8a2a7c

+ 191 - 62
FlatCAMApp.py

@@ -107,7 +107,7 @@ class App(QtCore.QObject):
     # ################## Version and VERSION DATE ##############################
     # ##########################################################################
     version = 8.97
-    version_date = "2019/09/15"
+    version_date = "2019/09/20"
     beta = True
 
     # current date now
@@ -359,10 +359,17 @@ class App(QtCore.QObject):
 
         if show_splash:
             splash_pix = QtGui.QPixmap('share/splash.png')
-            splash = QtWidgets.QSplashScreen(splash_pix, Qt.WindowStaysOnTopHint)
-            # splash.setMask(splash_pix.mask())
-            splash.show()
-            splash.showMessage(_("FlatCAM is initializing ..."),
+            self.splash = QtWidgets.QSplashScreen(splash_pix, Qt.WindowStaysOnTopHint)
+            # self.splash.setMask(splash_pix.mask())
+
+            # move splashscreen to the current monitor
+            desktop = QtWidgets.QApplication.desktop()
+            screen = desktop.screenNumber(QtGui.QCursor.pos())
+            current_screen_center = desktop.availableGeometry(screen).center()
+            self.splash.move(current_screen_center - self.splash.rect().center())
+
+            self.splash.show()
+            self.splash.showMessage(_("FlatCAM is initializing ..."),
                                alignment=Qt.AlignBottom | Qt.AlignLeft,
                                color=QtGui.QColor("gray"))
 
@@ -1536,24 +1543,24 @@ class App(QtCore.QObject):
         # ############# SETUP Plot Area #################
         # ###############################################
         if show_splash:
-            splash.showMessage(_("FlatCAM is initializing ...\n"
-                                 "Canvas initialization started."),
-                               alignment=Qt.AlignBottom | Qt.AlignLeft,
-                               color=QtGui.QColor("gray"))
+            self.splash.showMessage(_("FlatCAM is initializing ...\n"
+                                      "Canvas initialization started."),
+                                    alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                    color=QtGui.QColor("gray"))
         start_plot_time = time.time()   # debug
         self.plotcanvas = None
         self.app_cursor = None
         self.hover_shapes = None
         self.on_plotcanvas_setup()
         end_plot_time = time.time()
-        used_time = end_plot_time - start_plot_time
-        self.log.debug("Finished Canvas initialization in %s seconds." % str(used_time))
+        self.used_time = end_plot_time - start_plot_time
+        self.log.debug("Finished Canvas initialization in %s seconds." % str(self.used_time))
         if show_splash:
-            splash.showMessage('%s: %ssec' % (_("FlatCAM is initializing ...\n"
-                                                "Canvas initialization started.\n"
-                                                "Canvas initialization finished in"), '%.2f' % used_time),
-                               alignment=Qt.AlignBottom | Qt.AlignLeft,
-                               color=QtGui.QColor("gray"))
+            self.splash.showMessage('%s: %ssec' % (_("FlatCAM is initializing ...\n"
+                                                     "Canvas initialization started.\n"
+                                                     "Canvas initialization finished in"), '%.2f' % self.used_time),
+                                    alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                    color=QtGui.QColor("gray"))
         self.ui.splitter.setStretchFactor(1, 2)
 
         # to use for tools like Measurement tool who depends on the event sources who are changed inside the Editors
@@ -1695,6 +1702,7 @@ class App(QtCore.QObject):
         self.ui.menuview_zoom_fit.triggered.connect(self.on_zoom_fit)
         self.ui.menuview_zoom_in.triggered.connect(self.on_zoom_in)
         self.ui.menuview_zoom_out.triggered.connect(self.on_zoom_out)
+        self.ui.menuview_replot.triggered.connect(self.plot_all)
 
         self.ui.menuview_toggle_code_editor.triggered.connect(self.on_toggle_code_editor)
         self.ui.menuview_toggle_fscreen.triggered.connect(self.on_fullscreen)
@@ -2145,6 +2153,22 @@ class App(QtCore.QObject):
 
         self.myKeywords = self.tcl_commands_list + self.ordinary_keywords + self.tcl_keywords
 
+        self.default_autocomplete_keywords = [
+            'all', 'angle_x', 'angle_y', 'axis', 'axisoffset', 'box', 'center_x', 'center_y',
+            'columns', 'combine', 'connect', 'contour', 'depthperpass', 'dia', 'diatol', 'dist',
+            'drilled_dias', 'drillz', 'pp',
+            'gridoffsety', 'gridx', 'gridy', 'has_offset', 'holes', 'margin', 'method',
+            'milled_dias',
+            'minoffset', 'multidepth', 'name', 'offset', 'opt_type', 'order', 'outname',
+            'overlap', 'passes', 'postamble', 'ppname_e', 'ppname_g', 'preamble', 'radius', 'ref',
+            'rest', 'rows', 'scale_factor', 'spacing_columns', 'spacing_rows', 'spindlespeed',
+            'use_threads', 'value', 'x', 'x0', 'x1', 'y', 'y0', 'y1', 'z_cut', 'z_move',
+            'default', 'feedrate_z', 'grbl_11', 'grbl_laser', 'hpgl', 'line_xyz', 'marlin',
+            'Paste_1', 'Repetier', 'Toolchange_Custom', 'Roland_MDX_20', 'Toolchange_manual',
+            'Toolchange_Probe_MACH3', 'dwell', 'dwelltime', 'toolchange_xy', 'iso_type',
+            'Desktop', 'FlatPrj', 'FlatConfig', 'Users', 'Documents', 'My Documents', 'Marius'
+        ]
+
         # ####################################################################################
         # ####################### Shell SETUP ################################################
         # ####################################################################################
@@ -2203,20 +2227,6 @@ class App(QtCore.QObject):
         # self.f_parse = ParseFont(self)
         # self.parse_system_fonts()
 
-        # #####################################################################################
-        # ########################## START-UP ARGUMENTS #######################################
-        # #####################################################################################
-
-        # test if the program was started with a script as parameter
-        if self.cmd_line_shellfile:
-            try:
-                with open(self.cmd_line_shellfile, "r") as myfile:
-                    cmd_line_shellfile_text = myfile.read()
-                    self.shell._sysShell.exec_command(cmd_line_shellfile_text)
-            except Exception as ext:
-                print("ERROR: ", ext)
-                sys.exit(2)
-
         # #####################################################################################
         # ######################## Check for updates ##########################################
         # #####################################################################################
@@ -2264,10 +2274,10 @@ class App(QtCore.QObject):
 
         # List to store the objects that are currently loaded in FlatCAM
         # This list is updated on each object creation or object delete
-        self.all_objects_list = []
+        self.all_objects_list = list()
 
         # List to store the objects that are selected
-        self.sel_objects_list = []
+        self.sel_objects_list = list()
 
         # holds the key modifier if pressed (CTRL, SHIFT or ALT)
         self.key_modifiers = None
@@ -2365,19 +2375,46 @@ class App(QtCore.QObject):
 
         self.set_ui_title(name=_("New Project - Not saved"))
 
-        # finish the splash
-        # splash.finish(self.ui)
+        # disable the Excellon path optimizations made with Google OR-Tools if the app is run on a 32bit platform
+        current_platform = platform.architecture()[0]
+        if current_platform != '64bit':
+            self.ui.excellon_defaults_form.excellon_gen_group.excellon_optimization_radio.set_value('T')
+            self.ui.excellon_defaults_form.excellon_gen_group.excellon_optimization_radio.setDisabled(True)
 
         # ###############################################################################
         # ####################### Finished the CONSTRUCTOR ##############################
         # ###############################################################################
         App.log.debug("END of constructor. Releasing control.")
 
+        # #####################################################################################
+        # ########################## START-UP ARGUMENTS #######################################
+        # #####################################################################################
+
+        # test if the program was started with a script as parameter
+        if self.cmd_line_shellfile:
+            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"))
+                    cmd_line_shellfile_text = myfile.read()
+                    self.shell._sysShell.exec_command(cmd_line_shellfile_text)
+            except Exception as ext:
+                print("ERROR: ", ext)
+                sys.exit(2)
+
         # accept some type file as command line parameter: FlatCAM project, FlatCAM preferences or scripts
         # the path/file_name must be enclosed in quotes if it contain spaces
         if App.args:
             self.args_at_startup.emit(App.args)
 
+        # finish the splash
+        self.splash.finish(self.ui)
+
     @staticmethod
     def copy_and_overwrite(from_path, to_path):
         """
@@ -2433,10 +2470,10 @@ class App(QtCore.QObject):
                     if file_name == "":
                         self.inform.emit(_("Open Config file failed."))
                     else:
-                        # run_from_arg = True
+                        run_from_arg = True
                         # self.worker_task.emit({'fcn': self.open_config_file,
                         #                        'params': [file_name, run_from_arg]})
-                        self.open_config_file(file_name, run_from_arg=True)
+                        self.open_config_file(file_name, run_from_arg=run_from_arg)
                 except Exception as e:
                     log.debug("Could not open FlatCAM Config file as App parameter due: %s" % str(e))
 
@@ -3885,9 +3922,12 @@ class App(QtCore.QObject):
                     # "<b>{down}</B> area &nbsp;&nbsp;&nbsp;&nbsp;"
                     "<a href = \"https://bitbucket.org/jpcgt/flatcam/downloads/\"><b>{down}</B></a><BR>"
                     # "<b> {issue}</B> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
-                    "<a href = \"https://bitbucket.org/jpcgt/flatcam/issues?status=new&status=open/\"><B>{issue}</B></a><BR>".
-                        format(title=_("2D Computer-Aided Printed Circuit Board Manufacturing"),
-                               devel=_("Development"), down=_("DOWNLOAD"), issue=_("Issue tracker"))
+                    "<a href = \"https://bitbucket.org/jpcgt/flatcam/issues?status=new&status=open/\">"
+                    "<B>{issue}</B></a><BR>".format(
+                        title=_("2D Computer-Aided Printed Circuit Board Manufacturing"),
+                        devel=_("Development"),
+                        down=_("DOWNLOAD"),
+                        issue=_("Issue tracker"))
                 )
                 title.setOpenExternalLinks(True)
 
@@ -3897,7 +3937,8 @@ class App(QtCore.QObject):
                 description_label = QtWidgets.QLabel(
                     "FlatCAM {version} {beta} ({date}) - {arch}<br>"
                     "<a href = \"http://flatcam.org/\">http://flatcam.org</a><br>".format(
-                        version=version,beta=('BETA' if beta else ''),
+                        version=version,
+                        beta=('BETA' if beta else ''),
                         date=version_date,
                         arch=platform.architecture()[0])
                 )
@@ -3975,31 +4016,83 @@ class App(QtCore.QObject):
                 self.splash_tab_layout.addWidget(logo, stretch=0)
                 self.splash_tab_layout.addWidget(title, stretch=1)
 
-                self.prog_grid_lay = QtWidgets.QGridLayout()
+                pal = QtGui.QPalette()
+                pal.setColor(QtGui.QPalette.Background, Qt.white)
+
+                self.prog_form_lay = QtWidgets.QFormLayout()
+                self.prog_form_lay.setHorizontalSpacing(20)
 
                 prog_widget = QtWidgets.QWidget()
-                prog_widget.setLayout(self.prog_grid_lay)
-                self.programmmers_tab_layout.addWidget(prog_widget)
-                self.programmmers_tab_layout.addStretch()
-
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Programmer")), 0, 0)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Status")), 0, 1)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Juan Pablo Caram"), 1, 0)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % _("Program Author")), 1, 1)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Denis Hayrullin"), 2, 0)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 2, 1)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Kamil Sopko"), 3, 0)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 1)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 4, 0)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % _("Maintainer >=2019")), 4, 1)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Matthieu Berthomé"), 5, 0)
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 5, 1)
+                prog_widget.setLayout(self.prog_form_lay)
+                prog_scroll = QtWidgets.QScrollArea()
+                prog_scroll.setWidget(prog_widget)
+                prog_scroll.setWidgetResizable(True)
+                prog_scroll.setFrameShape(QtWidgets.QFrame.NoFrame)
+                prog_scroll.setPalette(pal)
+
+                self.programmmers_tab_layout.addWidget(prog_scroll)
+
+                self.prog_form_lay.addRow(QtWidgets.QLabel('<b>%s</b>' % _("Programmer")),
+                                          QtWidgets.QLabel('<b>%s</b>' % _("Status")))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Juan Pablo Caram"),
+                                          QtWidgets.QLabel('%s' % _("Program Author")))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Denis Hayrullin"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Kamil Sopko"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Marius Stanciu"),
+                                          QtWidgets.QLabel('%s' % _("Maintainer >=2019")))
+                self.prog_form_lay.addRow(QtWidgets.QLabel(''))
+
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Alexandru Lazar"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Matthieu Berthomé"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Mike Evans"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Victor Benso"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel(''))
+
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Barnaby Walters"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Jørn Sandvik Nilsson"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Lei Zheng"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Marco A Quezada"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel(''))
+
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Cedric Dussud"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Chris Hemingway"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Damian Wrobel"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Daniel Sallin"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel(''))
+
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Bruno Vunderl"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Gonzalo Lopez"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Jakob Staudt"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Mike Smith"))
+
+                self.prog_form_lay.addRow(QtWidgets.QLabel(''))
+
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Lubos Medovarsky"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Steve Martina"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Thomas Duffin"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel(''))
+
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@Idechix"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@SM"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@grbf"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@Symonty"))
+                self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@mgix"))
 
                 self.translator_grid_lay = QtWidgets.QGridLayout()
+
+                # trans_widget = QtWidgets.QWidget()
+                # trans_widget.setLayout(self.translator_grid_lay)
+                # self.translators_tab_layout.addWidget(trans_widget)
+                # self.translators_tab_layout.addStretch()
+
                 trans_widget = QtWidgets.QWidget()
                 trans_widget.setLayout(self.translator_grid_lay)
-                self.translators_tab_layout.addWidget(trans_widget)
-                self.translators_tab_layout.addStretch()
+                trans_scroll = QtWidgets.QScrollArea()
+                trans_scroll.setWidget(trans_widget)
+                trans_scroll.setWidgetResizable(True)
+                trans_scroll.setFrameShape(QtWidgets.QFrame.NoFrame)
+                trans_scroll.setPalette(pal)
+                self.translators_tab_layout.addWidget(trans_scroll)
 
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Language")), 0, 0)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Translator")), 0, 1)
@@ -4020,7 +4113,7 @@ class App(QtCore.QObject):
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Translation)"), 5, 1)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 5, 2)
                 self.translator_grid_lay.setColumnStretch(0, 0)
-                # self.translators_tab_layout.addStretch()
+                self.translators_tab_layout.addStretch()
 
                 self.license_tab_layout.addWidget(license_label)
                 self.license_tab_layout.addStretch()
@@ -7299,8 +7392,10 @@ class App(QtCore.QObject):
     def select_objects(self, key=None):
         # list where we store the overlapped objects under our mouse left click position
         objects_under_the_click_list = []
+
         # Populate the list with the overlapped objects on the click position
         curr_x, curr_y = self.pos
+
         for obj in self.all_objects_list:
             if (curr_x >= obj.options['xmin']) and (curr_x <= obj.options['xmax']) and \
                     (curr_y >= obj.options['ymin']) and (curr_y <= obj.options['ymax']):
@@ -7314,6 +7409,7 @@ class App(QtCore.QObject):
             # because we selected "nothing"
             if not objects_under_the_click_list:
                 self.collection.set_all_inactive()
+
                 # delete the possible selection box around a possible selected object
                 self.delete_selection_shape()
 
@@ -7330,7 +7426,6 @@ class App(QtCore.QObject):
                     self.inform.emit("")
                 else:
                     self.call_source = 'app'
-
             else:
                 # case when there is only an object under the click and we toggle it
                 if len(objects_under_the_click_list) == 1:
@@ -7625,7 +7720,7 @@ class App(QtCore.QObject):
                 except AttributeError:
                     pass
 
-        # tcl needs to be reinitialized, otherwise  old shell variables etc  remains
+        # tcl needs to be reinitialized, otherwise old shell variables etc  remains
         self.init_tcl()
 
         self.delete_selection_shape()
@@ -7713,6 +7808,11 @@ class App(QtCore.QObject):
             filenames = [str(filename) for filename in filenames]
         else:
             filenames = [name]
+            self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
+                                                         "Canvas initialization finished in"), '%.2f' % self.used_time,
+                                                       _("Opening Gerber file.")),
+                                    alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                    color=QtGui.QColor("gray"))
 
         if len(filenames) == 0:
             self.inform.emit('[WARNING_NOTCL] %s' %
@@ -7744,6 +7844,11 @@ class App(QtCore.QObject):
             filenames = [str(filename) for filename in filenames]
         else:
             filenames = [str(name)]
+            self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
+                                                         "Canvas initialization finished in"), '%.2f' % self.used_time,
+                                                       _("Opening Excellon file.")),
+                                    alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                    color=QtGui.QColor("gray"))
 
         if len(filenames) == 0:
             self.inform.emit('[WARNING_NOTCL]%s' %
@@ -7779,6 +7884,11 @@ class App(QtCore.QObject):
             filenames = [str(filename) for filename in filenames]
         else:
             filenames = [name]
+            self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
+                                                         "Canvas initialization finished in"), '%.2f' % self.used_time,
+                                                       _("Opening G-Code file.")),
+                                    alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                    color=QtGui.QColor("gray"))
 
         if len(filenames) == 0:
             self.inform.emit('[WARNING_NOTCL] %s' %
@@ -8433,6 +8543,11 @@ class App(QtCore.QObject):
 
         if name:
             filename = name
+            self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
+                                                         "Canvas initialization finished in"), '%.2f' % self.used_time,
+                                                       _("Executing FlatCAMScript file.")),
+                                    alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                    color=QtGui.QColor("gray"))
         else:
             _filter_ = "TCL script (*.FlatScript);;TCL script (*.TCL);;TCL script (*.TXT);;All Files (*.*)"
             try:
@@ -9602,6 +9717,12 @@ class App(QtCore.QObject):
         """
         App.log.debug("Opening config file: " + filename)
 
+        if run_from_arg:
+            self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
+                                                         "Canvas initialization finished in"), '%.2f' % self.used_time,
+                                                       _("Opening FlatCAM Config file.")),
+                                    alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                    color=QtGui.QColor("gray"))
         # add the tab if it was closed
         self.ui.plot_tab_area.addTab(self.ui.cncjob_tab, _("Code Editor"))
         # first clear previous text in text editor (if any)
@@ -9649,6 +9770,13 @@ class App(QtCore.QObject):
         if cli is None:
             self.set_ui_title(name=_("Loading Project ... Please Wait ..."))
 
+        if run_from_arg:
+            self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
+                                                         "Canvas initialization finished in"), '%.2f' % self.used_time,
+                                                       _("Opening FlatCAM Project file.")),
+                                    alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                    color=QtGui.QColor("gray"))
+
         # Open and parse an uncompressed Project file
         try:
             f = open(filename, 'r')
@@ -9789,6 +9917,7 @@ class App(QtCore.QObject):
         :return: None
         """
         self.log.debug("Plot_all()")
+        self.inform.emit('[success] %s...' % _("Redrawing all objects"))
 
         for obj in self.collection.get_list():
             def worker_task(obj):

+ 11 - 11
ObjectCollection.py

@@ -210,6 +210,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
         QtCore.QAbstractItemModel.__init__(self)
 
+        self.app = app
+
         # ## Icons for the list view
         self.icons = {}
         for kind in ObjectCollection.icon_files:
@@ -243,10 +245,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         # same as above only for objects that are plotted
         self.plot_promises = set()
 
-        self.app = app
-
         # ## View
-        self.view = KeySensitiveListView(app)
+        self.view = KeySensitiveListView(self.app)
         self.view.setModel(self)
 
         self.view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
@@ -606,14 +606,6 @@ class ObjectCollection(QtCore.QAbstractItemModel):
     def delete_all(self):
         FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
 
-        self.beginResetModel()
-
-        self.checked_indexes = []
-        for group in self.root_item.child_items:
-            group.remove_children()
-
-        self.endResetModel()
-
         self.app.plotcanvas.redraw()
 
         self.app.all_objects_list.clear()
@@ -630,6 +622,14 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
         self.app.film_tool.reset_fields()
 
+        self.beginResetModel()
+
+        self.checked_indexes = []
+        for group in self.root_item.child_items:
+            group.remove_children()
+
+        self.endResetModel()
+
     def get_active(self):
         """
         Returns the active object or None

+ 12 - 0
README.md

@@ -9,6 +9,13 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+17.09.2019
+
+- added more programmers that contributed to FlatCAM over the years, in the "About FlatCAM" -> Programmers window
+- fixed issue #315 where a script run with the --shellfile argument crashed the program if it contained a TclCommand New
+- added messages in the Splash Screen when running FlatCAM with arguments at startup
+- fixed issue #313 where TclCommand drillcncjob is spitting errors in Tcl Shell which should be ignored
+
 16.09.2019
 
 - modified the TclCommand New so it will no longer close all tabs when called (it closed the Code Editor tab which may have been holding the code that run)
@@ -20,6 +27,11 @@ CAD program, and create G-Code for Isolation routing.
 - fixed some issues recently introduced in the TclCommands CNCJob, DrillCNCJob and write_gcode; changed some parameters names
 - fixed issue in the Laser postprocessor where the laser was turned on as soon as the GCode started creating an unwanted cut up until the job start
 - added new links in Menu -> Help (Excellon, Gerber specifications and a Report Bug)
+- made the splashscreen to be showed on the current monitor on systems with multiple monitors
+- added a new entry in Menu -> View -> Redraw All which is doing what the name says: redraw all loaded objects
+- fixed issue where in TCl Shell the Windows paths were not understood due of backslash symbol understood as escape symbol instead of path separator
+- made sure that in for the TclCommand cncjob and for the drillcncjob if one of the args is stated but no value then the value used will be the default one
+- made available the TSA algorithm for drill path optimization when the used OS is 64bit. When used OS is 32bit the only available algorithm is TSA
 
 15.09.2019
 

+ 278 - 283
camlib.py

@@ -5867,318 +5867,313 @@ class CNCjob(Geometry):
 
         self.app.inform.emit('%s...' %
                              _("Starting G-Code"))
-        current_platform = platform.architecture()[0]
-        if current_platform == '64bit':
-            if excellon_optimization_type == 'M':
-                log.debug("Using OR-Tools Metaheuristic Guided Local Search drill path optimization.")
-                if exobj.drills:
-                    for tool in tools:
-                        self.tool=tool
-                        self.postdata['toolC'] = exobj.tools[tool]["C"]
-                        self.tooldia = exobj.tools[tool]["C"]
 
+        if excellon_optimization_type == 'M':
+            log.debug("Using OR-Tools Metaheuristic Guided Local Search drill path optimization.")
+            if exobj.drills:
+                for tool in tools:
+                    self.tool=tool
+                    self.postdata['toolC'] = exobj.tools[tool]["C"]
+                    self.tooldia = exobj.tools[tool]["C"]
+
+                    if self.app.abort_flag:
+                        # graceful abort requested by the user
+                        raise FlatCAMApp.GracefulException
+
+                    # ###############################################
+                    # ############ Create the data. #################
+                    # ###############################################
+
+                    node_list = []
+                    locations = create_data_array()
+                    tsp_size = len(locations)
+                    num_routes = 1  # The number of routes, which is 1 in the TSP.
+                    # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
+                    depot = 0
+                    # Create routing model.
+                    if tsp_size > 0:
+                        manager = pywrapcp.RoutingIndexManager(tsp_size, num_routes, depot)
+                        routing = pywrapcp.RoutingModel(manager)
+                        search_parameters = pywrapcp.DefaultRoutingSearchParameters()
+                        search_parameters.local_search_metaheuristic = (
+                            routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
+
+                        # Set search time limit in milliseconds.
+                        if float(self.app.defaults["excellon_search_time"]) != 0:
+                            search_parameters.time_limit.seconds = int(
+                                float(self.app.defaults["excellon_search_time"]))
+                        else:
+                            search_parameters.time_limit.seconds = 3
+
+                        # Callback to the distance function. The callback takes two
+                        # arguments (the from and to node indices) and returns the distance between them.
+                        dist_between_locations = CreateDistanceCallback()
+                        dist_callback = dist_between_locations.Distance
+                        transit_callback_index = routing.RegisterTransitCallback(dist_callback)
+                        routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
+
+                        # Solve, returns a solution if any.
+                        assignment = routing.SolveWithParameters(search_parameters)
+
+                        if assignment:
+                            # Solution cost.
+                            log.info("Total distance: " + str(assignment.ObjectiveValue()))
+
+                            # Inspect solution.
+                            # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
+                            route_number = 0
+                            node = routing.Start(route_number)
+                            start_node = node
+
+                            while not routing.IsEnd(node):
+                                if self.app.abort_flag:
+                                    # graceful abort requested by the user
+                                    raise FlatCAMApp.GracefulException
+
+                                node_list.append(node)
+                                node = assignment.Value(routing.NextVar(node))
+                        else:
+                            log.warning('No solution found.')
+                    else:
+                        log.warning('Specify an instance greater than 0.')
+                    # ############################################# ##
+
+                    # Only if tool has points.
+                    if tool in points:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             raise FlatCAMApp.GracefulException
 
-                        # ###############################################
-                        # ############ Create the data. #################
-                        # ###############################################
-
-                        node_list = []
-                        locations = create_data_array()
-                        tsp_size = len(locations)
-                        num_routes = 1  # The number of routes, which is 1 in the TSP.
-                        # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
-                        depot = 0
-                        # Create routing model.
-                        if tsp_size > 0:
-                            manager = pywrapcp.RoutingIndexManager(tsp_size, num_routes, depot)
-                            routing = pywrapcp.RoutingModel(manager)
-                            search_parameters = pywrapcp.DefaultRoutingSearchParameters()
-                            search_parameters.local_search_metaheuristic = (
-                                routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
-
-                            # Set search time limit in milliseconds.
-                            if float(self.app.defaults["excellon_search_time"]) != 0:
-                                search_parameters.time_limit.seconds = int(
-                                    float(self.app.defaults["excellon_search_time"]))
-                            else:
-                                search_parameters.time_limit.seconds = 3
-
-                            # Callback to the distance function. The callback takes two
-                            # arguments (the from and to node indices) and returns the distance between them.
-                            dist_between_locations = CreateDistanceCallback()
-                            dist_callback = dist_between_locations.Distance
-                            transit_callback_index = routing.RegisterTransitCallback(dist_callback)
-                            routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
-
-                            # Solve, returns a solution if any.
-                            assignment = routing.SolveWithParameters(search_parameters)
-
-                            if assignment:
-                                # Solution cost.
-                                log.info("Total distance: " + str(assignment.ObjectiveValue()))
-
-                                # Inspect solution.
-                                # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
-                                route_number = 0
-                                node = routing.Start(route_number)
-                                start_node = node
-
-                                while not routing.IsEnd(node):
-                                    if self.app.abort_flag:
-                                        # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
-
-                                    node_list.append(node)
-                                    node = assignment.Value(routing.NextVar(node))
-                            else:
-                                log.warning('No solution found.')
+                        # Tool change sequence (optional)
+                        if toolchange:
+                            gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
+                            gcode += self.doformat(p.spindle_code)  # Spindle start
+                            if self.dwell is True:
+                                gcode += self.doformat(p.dwell_code)  # Dwell time
                         else:
-                            log.warning('Specify an instance greater than 0.')
-                        # ############################################# ##
-
-                        # Only if tool has points.
-                        if tool in points:
-                            if self.app.abort_flag:
-                                # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
-
-                            # Tool change sequence (optional)
-                            if toolchange:
-                                gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
-                                gcode += self.doformat(p.spindle_code)  # Spindle start
-                                if self.dwell is True:
-                                    gcode += self.doformat(p.dwell_code)  # Dwell time
-                            else:
-                                gcode += self.doformat(p.spindle_code)
-                                if self.dwell is True:
-                                    gcode += self.doformat(p.dwell_code)  # Dwell time
+                            gcode += self.doformat(p.spindle_code)
+                            if self.dwell is True:
+                                gcode += self.doformat(p.dwell_code)  # Dwell time
 
-                            if self.units == 'MM':
-                                current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
-                            else:
-                                current_tooldia = float('%.4f' % float(exobj.tools[tool]["C"]))
+                        if self.units == 'MM':
+                            current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
+                        else:
+                            current_tooldia = float('%.4f' % float(exobj.tools[tool]["C"]))
 
-                            self.app.inform.emit(
-                                '%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
-                                               str(current_tooldia),
-                                               str(self.units))
-                            )
+                        self.app.inform.emit(
+                            '%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
+                                           str(current_tooldia),
+                                           str(self.units))
+                        )
 
-                            # TODO apply offset only when using the GUI, for TclCommand this will create an error
-                            # because the values for Z offset are created in build_ui()
-                            try:
-                                z_offset = float(self.tool_offset[current_tooldia]) * (-1)
-                            except KeyError:
-                                z_offset = 0
-                            self.z_cut += z_offset
-
-                            self.coordinates_type = self.app.defaults["cncjob_coords_type"]
-                            if self.coordinates_type == "G90":
-                                # Drillling! for Absolute coordinates type G90
-                                # variables to display the percentage of work done
-                                geo_len = len(node_list)
-                                disp_number = 0
-                                old_disp_number = 0
-                                log.warning("Number of drills for which to generate GCode: %s" % str(geo_len))
-
-                                loc_nr = 0
-                                for k in node_list:
-                                    if self.app.abort_flag:
-                                        # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
-
-                                    locx = locations[k][0]
-                                    locy = locations[k][1]
-
-                                    gcode += self.doformat(p.rapid_code, x=locx, y=locy)
-                                    gcode += self.doformat(p.down_code, x=locx, y=locy)
-
-                                    measured_down_distance += abs(self.z_cut) + abs(self.z_move)
-
-                                    if self.f_retract is False:
-                                        gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
-                                        measured_up_to_zero_distance += abs(self.z_cut)
-                                        measured_lift_distance += abs(self.z_move)
-                                    else:
-                                        measured_lift_distance += abs(self.z_cut) + abs(self.z_move)
+                        # TODO apply offset only when using the GUI, for TclCommand this will create an error
+                        # because the values for Z offset are created in build_ui()
+                        try:
+                            z_offset = float(self.tool_offset[current_tooldia]) * (-1)
+                        except KeyError:
+                            z_offset = 0
+                        self.z_cut += z_offset
 
-                                    gcode += self.doformat(p.lift_code, x=locx, y=locy)
-                                    measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
-                                    self.oldx = locx
-                                    self.oldy = locy
+                        self.coordinates_type = self.app.defaults["cncjob_coords_type"]
+                        if self.coordinates_type == "G90":
+                            # Drillling! for Absolute coordinates type G90
+                            # variables to display the percentage of work done
+                            geo_len = len(node_list)
+                            disp_number = 0
+                            old_disp_number = 0
+                            log.warning("Number of drills for which to generate GCode: %s" % str(geo_len))
 
-                                    loc_nr += 1
-                                    disp_number = int(np.interp(loc_nr, [0, geo_len], [0, 100]))
+                            loc_nr = 0
+                            for k in node_list:
+                                if self.app.abort_flag:
+                                    # graceful abort requested by the user
+                                    raise FlatCAMApp.GracefulException
 
-                                    if old_disp_number < disp_number <= 100:
-                                        self.app.proc_container.update_view_text(' %d%%' % disp_number)
-                                        old_disp_number = disp_number
+                                locx = locations[k][0]
+                                locy = locations[k][1]
 
-                            else:
-                                self.app.inform.emit('[ERROR_NOTCL] %s...' %
-                                                     _('G91 coordinates not implemented'))
-                                return 'fail'
-                else:
-                    log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
-                              "The loaded Excellon file has no drills ...")
-                    self.app.inform.emit('[ERROR_NOTCL] %s...' %
-                                         _('The loaded Excellon file has no drills'))
-                    return 'fail'
+                                gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+                                gcode += self.doformat(p.down_code, x=locx, y=locy)
 
-                log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
-            elif excellon_optimization_type == 'B':
-                log.debug("Using OR-Tools Basic drill path optimization.")
-                if exobj.drills:
-                    for tool in tools:
+                                measured_down_distance += abs(self.z_cut) + abs(self.z_move)
+
+                                if self.f_retract is False:
+                                    gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
+                                    measured_up_to_zero_distance += abs(self.z_cut)
+                                    measured_lift_distance += abs(self.z_move)
+                                else:
+                                    measured_lift_distance += abs(self.z_cut) + abs(self.z_move)
+
+                                gcode += self.doformat(p.lift_code, x=locx, y=locy)
+                                measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
+                                self.oldx = locx
+                                self.oldy = locy
+
+                                loc_nr += 1
+                                disp_number = int(np.interp(loc_nr, [0, geo_len], [0, 100]))
+
+                                if old_disp_number < disp_number <= 100:
+                                    self.app.proc_container.update_view_text(' %d%%' % disp_number)
+                                    old_disp_number = disp_number
+
+                        else:
+                            self.app.inform.emit('[ERROR_NOTCL] %s...' %
+                                                 _('G91 coordinates not implemented'))
+                            return 'fail'
+            else:
+                log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
+                          "The loaded Excellon file has no drills ...")
+                self.app.inform.emit('[ERROR_NOTCL] %s...' %
+                                     _('The loaded Excellon file has no drills'))
+                return 'fail'
+
+            log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
+        elif excellon_optimization_type == 'B':
+            log.debug("Using OR-Tools Basic drill path optimization.")
+            if exobj.drills:
+                for tool in tools:
+                    if self.app.abort_flag:
+                        # graceful abort requested by the user
+                        raise FlatCAMApp.GracefulException
+
+                    self.tool=tool
+                    self.postdata['toolC']=exobj.tools[tool]["C"]
+                    self.tooldia = exobj.tools[tool]["C"]
+
+                    # ############################################# ##
+                    node_list = []
+                    locations = create_data_array()
+                    tsp_size = len(locations)
+                    num_routes = 1  # The number of routes, which is 1 in the TSP.
+
+                    # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
+                    depot = 0
+
+                    # Create routing model.
+                    if tsp_size > 0:
+                        manager = pywrapcp.RoutingIndexManager(tsp_size, num_routes, depot)
+                        routing = pywrapcp.RoutingModel(manager)
+                        search_parameters = pywrapcp.DefaultRoutingSearchParameters()
+
+                        # Callback to the distance function. The callback takes two
+                        # arguments (the from and to node indices) and returns the distance between them.
+                        dist_between_locations = CreateDistanceCallback()
+                        dist_callback = dist_between_locations.Distance
+                        transit_callback_index = routing.RegisterTransitCallback(dist_callback)
+                        routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
+
+                        # Solve, returns a solution if any.
+                        assignment = routing.SolveWithParameters(search_parameters)
+
+                        if assignment:
+                            # Solution cost.
+                            log.info("Total distance: " + str(assignment.ObjectiveValue()))
+
+                            # Inspect solution.
+                            # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
+                            route_number = 0
+                            node = routing.Start(route_number)
+                            start_node = node
+
+                            while not routing.IsEnd(node):
+                                node_list.append(node)
+                                node = assignment.Value(routing.NextVar(node))
+                        else:
+                            log.warning('No solution found.')
+                    else:
+                        log.warning('Specify an instance greater than 0.')
+                    # ############################################# ##
+
+                    # Only if tool has points.
+                    if tool in points:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             raise FlatCAMApp.GracefulException
 
-                        self.tool=tool
-                        self.postdata['toolC']=exobj.tools[tool]["C"]
-                        self.tooldia = exobj.tools[tool]["C"]
-
-                        # ############################################# ##
-                        node_list = []
-                        locations = create_data_array()
-                        tsp_size = len(locations)
-                        num_routes = 1  # The number of routes, which is 1 in the TSP.
-
-                        # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
-                        depot = 0
-
-                        # Create routing model.
-                        if tsp_size > 0:
-                            manager = pywrapcp.RoutingIndexManager(tsp_size, num_routes, depot)
-                            routing = pywrapcp.RoutingModel(manager)
-                            search_parameters = pywrapcp.DefaultRoutingSearchParameters()
-
-                            # Callback to the distance function. The callback takes two
-                            # arguments (the from and to node indices) and returns the distance between them.
-                            dist_between_locations = CreateDistanceCallback()
-                            dist_callback = dist_between_locations.Distance
-                            transit_callback_index = routing.RegisterTransitCallback(dist_callback)
-                            routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
-
-                            # Solve, returns a solution if any.
-                            assignment = routing.SolveWithParameters(search_parameters)
-
-                            if assignment:
-                                # Solution cost.
-                                log.info("Total distance: " + str(assignment.ObjectiveValue()))
-
-                                # Inspect solution.
-                                # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
-                                route_number = 0
-                                node = routing.Start(route_number)
-                                start_node = node
-
-                                while not routing.IsEnd(node):
-                                    node_list.append(node)
-                                    node = assignment.Value(routing.NextVar(node))
-                            else:
-                                log.warning('No solution found.')
+                        # Tool change sequence (optional)
+                        if toolchange:
+                            gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
+                            gcode += self.doformat(p.spindle_code)  # Spindle start)
+                            if self.dwell is True:
+                                gcode += self.doformat(p.dwell_code)  # Dwell time
                         else:
-                            log.warning('Specify an instance greater than 0.')
-                        # ############################################# ##
-
-                        # Only if tool has points.
-                        if tool in points:
-                            if self.app.abort_flag:
-                                # graceful abort requested by the user
-                                raise FlatCAMApp.GracefulException
-
-                            # Tool change sequence (optional)
-                            if toolchange:
-                                gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
-                                gcode += self.doformat(p.spindle_code)  # Spindle start)
-                                if self.dwell is True:
-                                    gcode += self.doformat(p.dwell_code)  # Dwell time
-                            else:
-                                gcode += self.doformat(p.spindle_code)
-                                if self.dwell is True:
-                                    gcode += self.doformat(p.dwell_code)  # Dwell time
+                            gcode += self.doformat(p.spindle_code)
+                            if self.dwell is True:
+                                gcode += self.doformat(p.dwell_code)  # Dwell time
 
-                            if self.units == 'MM':
-                                current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
-                            else:
-                                current_tooldia = float('%.4f' % float(exobj.tools[tool]["C"]))
+                        if self.units == 'MM':
+                            current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
+                        else:
+                            current_tooldia = float('%.4f' % float(exobj.tools[tool]["C"]))
 
-                            self.app.inform.emit(
-                                '%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
-                                               str(current_tooldia),
-                                               str(self.units))
-                            )
+                        self.app.inform.emit(
+                            '%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
+                                           str(current_tooldia),
+                                           str(self.units))
+                        )
 
-                            # TODO apply offset only when using the GUI, for TclCommand this will create an error
-                            # because the values for Z offset are created in build_ui()
-                            try:
-                                z_offset = float(self.tool_offset[current_tooldia]) * (-1)
-                            except KeyError:
-                                z_offset = 0
-                            self.z_cut += z_offset
-
-                            self.coordinates_type = self.app.defaults["cncjob_coords_type"]
-                            if self.coordinates_type == "G90":
-                                # Drillling! for Absolute coordinates type G90
-                                # variables to display the percentage of work done
-                                geo_len = len(node_list)
-                                disp_number = 0
-                                old_disp_number = 0
-                                log.warning("Number of drills for which to generate GCode: %s" % str(geo_len))
-
-                                loc_nr = 0
-                                for k in node_list:
-                                    if self.app.abort_flag:
-                                        # graceful abort requested by the user
-                                        raise FlatCAMApp.GracefulException
-
-                                    locx = locations[k][0]
-                                    locy = locations[k][1]
-
-                                    gcode += self.doformat(p.rapid_code, x=locx, y=locy)
-                                    gcode += self.doformat(p.down_code, x=locx, y=locy)
-
-                                    measured_down_distance += abs(self.z_cut) + abs(self.z_move)
-
-                                    if self.f_retract is False:
-                                        gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
-                                        measured_up_to_zero_distance += abs(self.z_cut)
-                                        measured_lift_distance += abs(self.z_move)
-                                    else:
-                                        measured_lift_distance += abs(self.z_cut) + abs(self.z_move)
+                        # TODO apply offset only when using the GUI, for TclCommand this will create an error
+                        # because the values for Z offset are created in build_ui()
+                        try:
+                            z_offset = float(self.tool_offset[current_tooldia]) * (-1)
+                        except KeyError:
+                            z_offset = 0
+                        self.z_cut += z_offset
 
-                                    gcode += self.doformat(p.lift_code, x=locx, y=locy)
-                                    measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
-                                    self.oldx = locx
-                                    self.oldy = locy
+                        self.coordinates_type = self.app.defaults["cncjob_coords_type"]
+                        if self.coordinates_type == "G90":
+                            # Drillling! for Absolute coordinates type G90
+                            # variables to display the percentage of work done
+                            geo_len = len(node_list)
+                            disp_number = 0
+                            old_disp_number = 0
+                            log.warning("Number of drills for which to generate GCode: %s" % str(geo_len))
+
+                            loc_nr = 0
+                            for k in node_list:
+                                if self.app.abort_flag:
+                                    # graceful abort requested by the user
+                                    raise FlatCAMApp.GracefulException
 
-                                    loc_nr += 1
-                                    disp_number = int(np.interp(loc_nr, [0, geo_len], [0, 100]))
+                                locx = locations[k][0]
+                                locy = locations[k][1]
 
-                                    if old_disp_number < disp_number <= 100:
-                                        self.app.proc_container.update_view_text(' %d%%' % disp_number)
-                                        old_disp_number = disp_number
+                                gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+                                gcode += self.doformat(p.down_code, x=locx, y=locy)
 
-                            else:
-                                self.app.inform.emit('[ERROR_NOTCL] %s...' %
-                                                     _('G91 coordinates not implemented'))
-                                return 'fail'
-                else:
-                    log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
-                              "The loaded Excellon file has no drills ...")
-                    self.app.inform.emit('[ERROR_NOTCL] %s...' %
-                                         _('The loaded Excellon file has no drills'))
-                    return 'fail'
+                                measured_down_distance += abs(self.z_cut) + abs(self.z_move)
 
-                log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
+                                if self.f_retract is False:
+                                    gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
+                                    measured_up_to_zero_distance += abs(self.z_cut)
+                                    measured_lift_distance += abs(self.z_move)
+                                else:
+                                    measured_lift_distance += abs(self.z_cut) + abs(self.z_move)
+
+                                gcode += self.doformat(p.lift_code, x=locx, y=locy)
+                                measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
+                                self.oldx = locx
+                                self.oldy = locy
+
+                                loc_nr += 1
+                                disp_number = int(np.interp(loc_nr, [0, geo_len], [0, 100]))
+
+                                if old_disp_number < disp_number <= 100:
+                                    self.app.proc_container.update_view_text(' %d%%' % disp_number)
+                                    old_disp_number = disp_number
+
+                        else:
+                            self.app.inform.emit('[ERROR_NOTCL] %s...' %
+                                                 _('G91 coordinates not implemented'))
+                            return 'fail'
             else:
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _("Wrong optimization type selected."))
+                log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
+                          "The loaded Excellon file has no drills ...")
+                self.app.inform.emit('[ERROR_NOTCL] %s...' %
+                                     _('The loaded Excellon file has no drills'))
                 return 'fail'
+
+            log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
         else:
             log.debug("Using Travelling Salesman drill path optimization.")
             for tool in tools:

+ 17 - 11
flatcamGUI/FlatCAMGUI.py

@@ -372,6 +372,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.menuview_zoom_out = self.menuview.addAction(QtGui.QIcon('share/zoom_out32.png'), _("&Zoom Out\t-"))
         self.menuview.addSeparator()
 
+        # Replot all
+        self.menuview_replot = self.menuview.addAction(QtGui.QIcon('share/replot32.png'), _("Redraw All\tF5"))
+        self.menuview.addSeparator()
+
         self.menuview_toggle_code_editor = self.menuview.addAction(QtGui.QIcon('share/code_editor32.png'),
                                                                    _('Toggle Code Editor\tCTRL+E'))
         self.menuview.addSeparator()
@@ -5168,15 +5172,16 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
         self.excellon_general_label = QtWidgets.QLabel("<b>%s:</b>" % _("Excellon Optimization"))
         grid2.addWidget(self.excellon_general_label, 4, 0, 1, 2)
 
-        self.excellon_optimization_label = QtWidgets.QLabel(_('Algorithm:   '))
+        self.excellon_optimization_label = QtWidgets.QLabel(_('Algorithm:'))
         self.excellon_optimization_label.setToolTip(
             _("This sets the optimization type for the Excellon drill path.\n"
-              "If MH is checked then Google OR-Tools algorithm with MetaHeuristic\n"
-              "Guided Local Path is used. Default search time is 3sec.\n"
-              "Use set_sys excellon_search_time value Tcl Command to set other values.\n"
-              "If Basic is checked then Google OR-Tools Basic algorithm is used.\n"
+              "If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
+              "MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
+              "If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
+              "If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
+              "drill path optimization.\n"
               "\n"
-              "If DISABLED, then FlatCAM works in 32bit mode and it uses \n"
+              "If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
               "Travelling Salesman algorithm for path optimization.")
         )
         grid2.addWidget(self.excellon_optimization_label, 5, 0)
@@ -5187,12 +5192,13 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
                                                     orientation='vertical', stretch=False)
         self.excellon_optimization_radio.setToolTip(
             _("This sets the optimization type for the Excellon drill path.\n"
-              "If MH is checked then Google OR-Tools algorithm with MetaHeuristic\n"
-              "Guided Local Path is used. Default search time is 3sec.\n"
-              "Use set_sys excellon_search_time value Tcl Command to set other values.\n"
-              "If Basic is checked then Google OR-Tools Basic algorithm is used.\n"
+              "If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
+              "MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
+              "If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
+              "If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
+              "drill path optimization.\n"
               "\n"
-              "If DISABLED, then FlatCAM works in 32bit mode and it uses \n"
+              "If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
               "Travelling Salesman algorithm for path optimization.")
         )
         grid2.addWidget(self.excellon_optimization_radio, 5, 1)

+ 7 - 0
flatcamTools/ToolShell.py

@@ -12,6 +12,7 @@ from PyQt5.QtGui import QTextCursor
 from PyQt5.QtWidgets import QVBoxLayout, QWidget
 from flatcamGUI.GUIElements import _BrowserTextEdit, _ExpandableTextEdit
 import html
+import sys
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -132,6 +133,12 @@ class TermWidget(QWidget):
         Re-implement in the child classes to actually execute command
         """
         text = str(self._edit.toPlainText())
+
+        # in Windows replace all backslash symbols '\' with '\\' slash because Windows paths are made with backslash
+        # and in Python single slash is the escape symbol
+        if sys.platform == 'win32':
+            text = text.replace('\\', '\\\\')
+
         self._append_to_browser('in', '> ' + text + '\n')
 
         if len(self._history) < 2 or self._history[-2] != text:  # don't insert duplicating items

+ 26 - 21
tclCommands/TclCommandCncjob.py

@@ -101,43 +101,48 @@ class TclCommandCncjob(TclCommandSignaled):
         obj = self.app.collection.get_by_name(str(name), isCaseSensitive=False)
 
         if obj is None:
-            if not muted:
+            if muted == 0:
                 self.raise_tcl_error("Object not found: %s" % str(name))
             else:
-                return
+                return "fail"
 
         if not isinstance(obj, FlatCAMGeometry):
-            if not muted:
+            if muted == 0:
                 self.raise_tcl_error('Expected FlatCAMGeometry, got %s %s.' % (str(name), type(obj)))
             else:
                 return
 
-        args["dia"] = args["dia"] if "dia" in args else obj.options["cnctooldia"]
+        args["dia"] = args["dia"] if "dia" in args and args["dia"] else obj.options["cnctooldia"]
 
-        args["z_cut"] = args["z_cut"] if "z_cut" in args else obj.options["cutz"]
-        args["z_move"] = args["z_move"] if "z_move" in args else obj.options["travelz"]
+        args["z_cut"] = args["z_cut"] if "z_cut" in args and args["z_cut"] else obj.options["cutz"]
+        args["z_move"] = args["z_move"] if "z_move" in args and args["z_move"] else obj.options["travelz"]
 
-        args["feedrate"] = args["feedrate"] if "feedrate" in args else obj.options["feedrate"]
-        args["feedrate_z"] = args["feedrate_z"] if "feedrate_z" in args else obj.options["feedrate_z"]
-        args["feedrate_rapid"] = args["feedrate_rapid"] if "feedrate_rapid" in args else obj.options["feedrate_rapid"]
+        args["feedrate"] = args["feedrate"] if "feedrate" in args and args["feedrate"] else obj.options["feedrate"]
+        args["feedrate_z"] = args["feedrate_z"] if "feedrate_z" in args and args["feedrate_z"] else \
+            obj.options["feedrate_z"]
+        args["feedrate_rapid"] = args["feedrate_rapid"] if "feedrate_rapid" in args and args["feedrate_rapid"] else \
+            obj.options["feedrate_rapid"]
 
-        args["multidepth"] = args["multidepth"] if "multidepth" in args else obj.options["multidepth"]
-        args["extracut"] = args["extracut"] if "extracut" in args else obj.options["extracut"]
-        args["depthperpass"] = args["depthperpass"] if "depthperpass" in args else obj.options["depthperpass"]
+        args["multidepth"] = args["multidepth"] if "multidepth" in args and args["multidepth"] else \
+            obj.options["multidepth"]
+        args["extracut"] = args["extracut"] if "extracut" in args and args["extracut"] else obj.options["extracut"]
+        args["depthperpass"] = args["depthperpass"] if "depthperpass" in args and args["depthperpass"] else \
+            obj.options["depthperpass"]
 
-        args["startz"] = args["startz"] if "startz" in args else \
+        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 else obj.options["endz"]
+        args["endz"] = args["endz"] if "endz" in args and args["endz"] else obj.options["endz"]
 
-        args["spindlespeed"] = args["spindlespeed"] if "spindlespeed" in args else None
-        args["dwell"] = args["dwell"] if "dwell" in args else obj.options["dwell"]
-        args["dwelltime"] = args["dwelltime"] if "dwelltime" in args else obj.options["dwelltime"]
+        args["spindlespeed"] = args["spindlespeed"] if "spindlespeed" in args and args["spindlespeed"] else None
+        args["dwell"] = args["dwell"] if "dwell" in args and args["dwell"] else obj.options["dwell"]
+        args["dwelltime"] = args["dwelltime"] if "dwelltime" in args and args["dwelltime"] else obj.options["dwelltime"]
 
-        args["pp"] = args["pp"] if "pp" in args else obj.options["ppname_g"]
+        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 else obj.options["toolchangez"]
-        args["toolchangexy"] = args["toolchangexy"] if "toolchangexy" in args else \
+        args["toolchangez"] = args["toolchangez"] if "toolchangez" in args and args["toolchangez"] else \
+            obj.options["toolchangez"]
+        args["toolchangexy"] = args["toolchangexy"] if "toolchangexy" in args and args["toolchangexy"] else \
             self.app.defaults["geometry_toolchangexy"]
 
         del args['name']
@@ -148,7 +153,7 @@ class TclCommandCncjob(TclCommandSignaled):
             else:
                 if args[arg] is None:
                     print(arg, args[arg])
-                    if not muted:
+                    if muted == 0:
                         self.raise_tcl_error('One of the command parameters that have to be not None, is None.\n'
                                              'The parameter that is None is in the default values found in the list \n'
                                              'generated by the TclCommand "list_sys geom". or in the arguments.')

+ 24 - 15
tclCommands/TclCommandDrillcncjob.py

@@ -92,13 +92,16 @@ class TclCommandDrillcncjob(TclCommandSignaled):
 
         obj = self.app.collection.get_by_name(name)
         if obj is None:
-            self.raise_tcl_error("Object not found: %s" % name)
+            if muted == 0:
+                self.raise_tcl_error("Object not found: %s" % name)
+            else:
+                return "fail"
 
         if not isinstance(obj, FlatCAMExcellon):
-            if not muted:
+            if muted == 0:
                 self.raise_tcl_error('Expected FlatCAMExcellon, got %s %s.' % (name, type(obj)))
             else:
-                return
+                return "fail"
 
         xmin = obj.options['xmin']
         ymin = obj.options['ymin']
@@ -136,11 +139,11 @@ class TclCommandDrillcncjob(TclCommandSignaled):
                                     nr_diameters -= 1
 
                     if nr_diameters > 0:
-                        if not muted:
+                        if muted == 0:
                             self.raise_tcl_error("One or more tool diameters of the drills to be drilled passed to the "
                                                  "TclCommand are not actual tool diameters in the Excellon object.")
                         else:
-                            return
+                            return "fail"
 
                     # make a string of diameters separated by comma; this is what generate_from_excellon_by_tool() is
                     # expecting as tools parameter
@@ -156,21 +159,26 @@ class TclCommandDrillcncjob(TclCommandSignaled):
                     tools = 'all'
             except Exception as e:
                 tools = 'all'
-                self.raise_tcl_error("Bad tools: %s" % str(e))
 
-            drillz = args["drillz"] if "drillz" in args else obj.options["drillz"]
-            toolchangez = args["toolchangez"] if "toolchangez" in args else obj.options["toolchangez"]
-            endz = args["endz"] if "endz" in args else obj.options["endz"]
+                if muted == 0:
+                    self.raise_tcl_error("Bad tools: %s" % str(e))
+                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 args["toolchange"] == 1 else False
-            opt_type = args["opt_type"] if "opt_type" in args else 'B'
+            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 else obj.options["travelz"]
-            job_obj.feedrate = args["feedrate"] if "feedrate" in args else obj.options["feedrate"]
+            job_obj.z_move = args["travelz"] if "travelz" in args and args["travelz"] else obj.options["travelz"]
+            job_obj.feedrate = args["feedrate"] if "feedrate" in args and args["feedrate"] else obj.options["feedrate"]
             job_obj.feedrate_rapid = args["feedrate_rapid"] \
-                if "feedrate_rapid" in args else obj.options["feedrate_rapid"]
+                if "feedrate_rapid" in args and args["feedrate_rapid"] else obj.options["feedrate_rapid"]
 
             job_obj.spindlespeed = args["spindlespeed"] if "spindlespeed" in args else None
-            job_obj.pp_excellon_name = args["pp"] if "pp" in args \
+            job_obj.pp_excellon_name = args["pp"] if "pp" in args and args["pp"] \
                 else obj.options["ppname_e"]
 
             job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
@@ -178,7 +186,8 @@ class TclCommandDrillcncjob(TclCommandSignaled):
 
             job_obj.options['type'] = 'Excellon'
 
-            job_obj.toolchangexy = args["toolchangexy"] if "toolchangexy" in args else obj.options["toolchangexy"]
+            job_obj.toolchangexy = args["toolchangexy"] if "toolchangexy" in args and args["toolchangexy"] else \
+                obj.options["toolchangexy"]
 
             job_obj.toolchange_xy_type = "excellon"
 

+ 2 - 2
tclCommands/TclCommandWriteGCode.py

@@ -90,10 +90,10 @@ class TclCommandWriteGCode(TclCommandSignaled):
         try:
             obj = self.app.collection.get_by_name(str(obj_name))
         except:
-            if not muted:
+            if muted == 0:
                 return "Could not retrieve object: %s" % obj_name
             else:
-                return
+                return "fail"
 
         try:
             obj.export_gcode(str(filename), str(preamble), str(postamble))