Преглед изворни кода

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

Beta - adding new command line parameter --shellvars
Marius Stanciu пре 6 година
родитељ
комит
5a224ddb66
5 измењених фајлова са 354 додато и 298 уклоњено
  1. 0 7
      FlatCAM.py
  2. 65 13
      FlatCAMApp.py
  3. 3 0
      README.md
  4. 285 278
      camlib.py
  5. 1 0
      config/configuration.txt

+ 0 - 7
FlatCAM.py

@@ -60,11 +60,4 @@ if __name__ == '__main__':
 
     fc = App()
 
-    if settings.contains("maximized_gui"):
-        maximized_ui = settings.value('maximized_gui', type=bool)
-        if maximized_ui is True:
-            fc.ui.showMaximized()
-        else:
-            fc.ui.show()
-
     sys.exit(app.exec_())

+ 65 - 13
FlatCAMApp.py

@@ -80,10 +80,14 @@ class App(QtCore.QObject):
 
     # Get Cmd Line Options
     cmd_line_shellfile = ''
-    cmd_line_help = "FlatCam.py --shellfile=<cmd_line_shellfile>"
+    cmd_line_shellvars = ''
+
+    cmd_line_help = "FlatCam.py --shellfile=<cmd_line_shellfile>\nFlatCam.py --shellvars=<cmd_line_shellvars"
     try:
         # Multiprocessing pool will spawn additional processes with 'multiprocessing-fork' flag
-        cmd_line_options, args = getopt.getopt(sys.argv[1:], "h:", ["shellfile=", "multiprocessing-fork="])
+        cmd_line_options, args = getopt.getopt(sys.argv[1:], "h:", ["shellfile=",
+                                                                    "shellvars=",
+                                                                    "multiprocessing-fork="])
     except getopt.GetoptError:
         print(cmd_line_help)
         sys.exit(2)
@@ -93,6 +97,8 @@ class App(QtCore.QObject):
             sys.exit()
         elif opt == '--shellfile':
             cmd_line_shellfile = arg
+        elif opt == '--shellvars':
+            cmd_line_shellvars = arg
 
     # ## Logging ###
     log = logging.getLogger('base')
@@ -1905,7 +1911,7 @@ class App(QtCore.QObject):
             self.on_excellon_options_button)
 
         # when there are arguments at application startup this get launched
-        self.args_at_startup[list].connect(lambda: self.on_startup_args())
+        self.args_at_startup[list].connect(self.on_startup_args)
 
         # connect the 'Apply' buttons from the Preferences/File Associations
         self.ui.fa_defaults_form.fa_excellon_group.exc_list_btn.clicked.connect(
@@ -2406,7 +2412,26 @@ class App(QtCore.QObject):
             except Exception as ext:
                 print("ERROR: ", ext)
                 sys.exit(2)
-
+        elif self.cmd_line_shellvars:
+            try:
+                with open(self.cmd_line_shellvars, "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,
+                            _("Reading Shell vars ...")),
+                                                alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                                color=QtGui.QColor("gray"))
+                    cmd_line_shellvars_text = myfile.read()
+                    for line in cmd_line_shellvars_text.splitlines():
+                        var, __, var_value = line.partition('=')
+                        var = var.replace(' ', '')
+                        var_value = var_value.replace(' ', '')
+                        command_tcl = 'set {var} {var_value}'.format(var=var, var_value=var_value)
+                        self.shell._sysShell.exec_command(command_tcl, no_echo=True)
+            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:
@@ -2415,6 +2440,18 @@ class App(QtCore.QObject):
         # finish the splash
         self.splash.finish(self.ui)
 
+        # #####################################################################################
+        # ########################## SHOW GUI #################################################
+        # #####################################################################################
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("maximized_gui"):
+            maximized_ui = settings.value('maximized_gui', type=bool)
+            if maximized_ui is True:
+                self.ui.showMaximized()
+            else:
+                self.ui.show()
+
     @staticmethod
     def copy_and_overwrite(from_path, to_path):
         """
@@ -2433,14 +2470,15 @@ class App(QtCore.QObject):
             from_new_path = os.path.dirname(os.path.realpath(__file__)) + '\\flatcamGUI\\VisPyData\\data'
             shutil.copytree(from_new_path, to_path)
 
-    def on_startup_args(self, args=None):
+    def on_startup_args(self, args):
         """
         This will process any arguments provided to the application at startup. Like trying to launch a file or project.
 
         :param args: a list containing the application args at startup
         :return: None
         """
-        if args:
+
+        if args is not None:
             args_to_process = args
         else:
             args_to_process = App.args
@@ -2491,6 +2529,13 @@ class App(QtCore.QObject):
                 except Exception as e:
                     log.debug("Could not open FlatCAM Script file as App parameter due: %s" % str(e))
 
+            elif 'quit' in argument or 'exit' in argument:
+                log.debug("App.on_startup_args() --> Quit event.")
+                sys.exit()
+
+            elif 'save' in argument:
+                log.debug("App.on_startup_args() --> Save event. App Defaults saved.")
+                self.save_defaults()
             else:
                 exc_list = self.ui.fa_defaults_form.fa_excellon_group.exc_list_text.get_value().split(',')
                 proc_arg = argument.lower()
@@ -3125,18 +3170,20 @@ class App(QtCore.QObject):
         self.display_tcl_error(text)
         raise self.TclErrorException(text)
 
-    def exec_command(self, text, no_plot=None):
+    def exec_command(self, text, no_echo=False):
         """
         Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
         Also handles execution in separated threads
 
         :param text: FlatCAM TclCommand with parameters
+        :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 if there was any
         """
 
         self.report_usage('exec_command')
 
-        result = self.exec_command_test(text, False)
+        result = self.exec_command_test(text, False, no_echo=no_echo)
 
         # MS: added this method call so the geometry is updated once the TCL command is executed
         # if no_plot is None:
@@ -3144,35 +3191,40 @@ class App(QtCore.QObject):
 
         return result
 
-    def exec_command_test(self, text, reraise=True):
+    def exec_command_test(self, text, reraise=True, no_echo=False):
         """
         Same as exec_command(...) with additional control over  exceptions.
         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 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
         """
 
         tcl_command_string = str(text)
 
         try:
-            self.shell.open_proccessing()  # Disables input box.
+            if no_echo is False:
+                self.shell.open_proccessing()  # Disables input box.
 
             result = self.tcl.eval(str(tcl_command_string))
-            if result != 'None':
+            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'))
-            self.shell.append_error('ERROR: ' + result + '\n')
+            if no_echo is False:
+                self.shell.append_error('ERROR: ' + result + '\n')
             # Show error in console and just return or in test raise exception
             if reraise:
                 raise e
         finally:
-            self.shell.close_proccessing()
+            if no_echo is False:
+                self.shell.close_proccessing()
             pass
         return result
 

+ 3 - 0
README.md

@@ -15,6 +15,9 @@ CAD program, and create G-Code for Isolation routing.
 - 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
+- fixed an bug where the pywrapcp name from Google OR-Tools is not defined; fix issue #316
+- if FlatCAM is started with the 'quit' or 'exit' as argument it will close immediately and it will close also another instance of FlatCAM that may be running
+- added a new command line parameter for FlatCAM named '--shellvars' which can load a text file with variables for Tcl Shell in the format: one variable assignment per line and looking like: 'a=3' without quotes
 
 16.09.2019
 

+ 285 - 278
camlib.py

@@ -5868,313 +5868,320 @@ class CNCjob(Geometry):
         self.app.inform.emit('%s...' %
                              _("Starting G-Code"))
 
-        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.')
-                    # ############################################# ##
+        current_platform = platform.architecture()[0]
+        if current_platform == '64bit':
+            used_excellon_optimization_type = excellon_optimization_type
+            if used_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"]
 
-                    # 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
-
-                        if self.units == 'MM':
-                            current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
+                        # ###############################################
+                        # ############ 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:
-                            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))
-                        )
-
-                        # 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)
+                            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
 
-                                measured_down_distance += abs(self.z_cut) + abs(self.z_move)
+                            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.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)
+                            self.app.inform.emit(
+                                '%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
+                                               str(current_tooldia),
+                                               str(self.units))
+                            )
 
-                                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
+                            # 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)
 
-                                loc_nr += 1
-                                disp_number = int(np.interp(loc_nr, [0, geo_len], [0, 100]))
+                                    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
 
-                                if old_disp_number < disp_number <= 100:
-                                    self.app.proc_container.update_view_text(' %d%%' % disp_number)
-                                    old_disp_number = disp_number
+                                    loc_nr += 1
+                                    disp_number = int(np.interp(loc_nr, [0, geo_len], [0, 100]))
 
-                        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'
+                                    if old_disp_number < disp_number <= 100:
+                                        self.app.proc_container.update_view_text(' %d%%' % disp_number)
+                                        old_disp_number = disp_number
 
-            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"]
+                            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'
 
-                    # ############################################# ##
-                    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.')
-                    # ############################################# ##
+                log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
 
-                    # Only if tool has points.
-                    if tool in points:
+            if used_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
 
-                        # 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
-
-                        if self.units == 'MM':
-                            current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
+                        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:
-                            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))
-                        )
-
-                        # 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]
+                            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.rapid_code, x=locx, y=locy)
-                                gcode += self.doformat(p.down_code, x=locx, y=locy)
+                            if self.units == 'MM':
+                                current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
+                            else:
+                                current_tooldia = float('%.4f' % float(exobj.tools[tool]["C"]))
 
-                                measured_down_distance += abs(self.z_cut) + abs(self.z_move)
+                            self.app.inform.emit(
+                                '%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
+                                               str(current_tooldia),
+                                               str(self.units))
+                            )
 
-                                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
+
+                            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)
 
-                                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
+                                    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]))
+                                    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
+                                    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'
+                            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 Basic Algorithm is: %s" % str(measured_distance))
+                log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
         else:
+            used_excellon_optimization_type = 'T'
+
+        if used_excellon_optimization_type == 'T':
             log.debug("Using Travelling Salesman drill path optimization.")
             for tool in tools:
                 if self.app.abort_flag:

+ 1 - 0
config/configuration.txt

@@ -1 +1,2 @@
 portable=False
+headless=False