Explorar o código

cleanups
implement TclCommand.TclCommandSignaled as proof of concept (not usefull)
bypass using threads within obj.generatecncjob(use_thread = False, **args)
reimplement some more shell commands to OOP style

Kamil Sopko %!s(int64=9) %!d(string=hai) anos
pai
achega
980638630d

+ 51 - 51
FlatCAMApp.py

@@ -651,7 +651,7 @@ class App(QtCore.QObject):
 
         pass
 
-    def raiseTclUnknownError(self, unknownException):
+    def raise_tcl_unknown_error(self, unknownException):
         """
         raise Exception if is different type  than TclErrorException
         :param unknownException:
@@ -659,11 +659,11 @@ class App(QtCore.QObject):
         """
 
         if not isinstance(unknownException, self.TclErrorException):
-            self.raiseTclError("Unknown error: %s" % str(unknownException))
+            self.raise_tcl_error("Unknown error: %s" % str(unknownException))
         else:
             raise unknownException
 
-    def raiseTclError(self, text):
+    def raise_tcl_error(self, text):
         """
         this method  pass exception from python into TCL as error, so we get stacktrace and reason
         :param text: text of error
@@ -2274,20 +2274,20 @@ class App(QtCore.QObject):
                 # 8     - 2*left + 2*right +2*top + 2*bottom
 
                 if name is None:
-                    self.raiseTclError('Argument name is missing.')
+                    self.raise_tcl_error('Argument name is missing.')
 
                 for key in kwa:
                     if key not in types:
-                        self.raiseTclError('Unknown parameter: %s' % key)
+                        self.raise_tcl_error('Unknown parameter: %s' % key)
                     try:
                         kwa[key] = types[key](kwa[key])
                     except Exception, e:
-                        self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
+                        self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
 
                 try:
                     obj = self.collection.get_by_name(str(name))
                 except:
-                    self.raiseTclError("Could not retrieve object: %s" % name)
+                    self.raise_tcl_error("Could not retrieve object: %s" % name)
 
                 # Get min and max data for each object as we just cut rectangles across X or Y
                 xmin, ymin, xmax, ymax = obj.bounds()
@@ -2337,7 +2337,7 @@ class App(QtCore.QObject):
                                        ymax + gapsize)
 
             except Exception as unknown:
-                self.raiseTclUnknownError(unknown)
+                self.raise_tcl_unknown_error(unknown)
 
         def mirror(name, *args):
             a, kwa = h(*args)
@@ -2624,26 +2624,26 @@ class App(QtCore.QObject):
                          }
 
                 if name is None:
-                    self.raiseTclError('Argument name is missing.')
+                    self.raise_tcl_error('Argument name is missing.')
 
                 for key in kwa:
                     if key not in types:
-                        self.raiseTclError('Unknown parameter: %s' % key)
+                        self.raise_tcl_error('Unknown parameter: %s' % key)
                     try:
                         kwa[key] = types[key](kwa[key])
                     except Exception, e:
-                        self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
+                        self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
 
                 try:
                     obj = self.collection.get_by_name(str(name))
                 except:
-                    self.raiseTclError("Could not retrieve object: %s" % name)
+                    self.raise_tcl_error("Could not retrieve object: %s" % name)
 
                 if obj is None:
-                    self.raiseTclError('Object not found: %s' % name)
+                    self.raise_tcl_error('Object not found: %s' % name)
 
                 if not isinstance(obj, FlatCAMExcellon):
-                    self.raiseTclError('Only Excellon objects can be drilled, got %s %s.' % (name,  type(obj)))
+                    self.raise_tcl_error('Only Excellon objects can be drilled, got %s %s.' % (name, type(obj)))
 
                 try:
                     # Get the tools from the list
@@ -2663,10 +2663,10 @@ class App(QtCore.QObject):
                     obj.app.new_object("cncjob", job_name, job_init)
 
                 except Exception, e:
-                    self.raiseTclError("Operation failed: %s" % str(e))
+                    self.raise_tcl_error("Operation failed: %s" % str(e))
 
             except Exception as unknown:
-                self.raiseTclUnknownError(unknown)
+                self.raise_tcl_unknown_error(unknown)
 
 
         def millholes(name=None, *args):
@@ -2684,43 +2684,43 @@ class App(QtCore.QObject):
                          'outname': str}
 
                 if name is None:
-                    self.raiseTclError('Argument name is missing.')
+                    self.raise_tcl_error('Argument name is missing.')
 
                 for key in kwa:
                     if key not in types:
-                        self.raiseTclError('Unknown parameter: %s' % key)
+                        self.raise_tcl_error('Unknown parameter: %s' % key)
                     try:
                         kwa[key] = types[key](kwa[key])
                     except Exception, e:
-                        self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
+                        self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
 
                 try:
                     if 'tools' in kwa:
                         kwa['tools'] = [x.strip() for x in kwa['tools'].split(",")]
                 except Exception as e:
-                    self.raiseTclError("Bad tools: %s" % str(e))
+                    self.raise_tcl_error("Bad tools: %s" % str(e))
 
                 try:
                     obj = self.collection.get_by_name(str(name))
                 except:
-                    self.raiseTclError("Could not retrieve object: %s" % name)
+                    self.raise_tcl_error("Could not retrieve object: %s" % name)
 
                 if obj is None:
-                    self.raiseTclError("Object not found: %s" % name)
+                    self.raise_tcl_error("Object not found: %s" % name)
 
                 if not isinstance(obj, FlatCAMExcellon):
-                    self.raiseTclError('Only Excellon objects can be mill drilled, got %s %s.' % (name,  type(obj)))
+                    self.raise_tcl_error('Only Excellon objects can be mill drilled, got %s %s.' % (name, type(obj)))
 
                 try:
                     success, msg = obj.generate_milling(**kwa)
                 except Exception as e:
-                    self.raiseTclError("Operation failed: %s" % str(e))
+                    self.raise_tcl_error("Operation failed: %s" % str(e))
 
                 if not success:
-                    self.raiseTclError(msg)
+                    self.raise_tcl_error(msg)
 
             except Exception as unknown:
-                self.raiseTclUnknownError(unknown)
+                self.raise_tcl_unknown_error(unknown)
 
         def exteriors(name=None, *args):
             '''
@@ -2735,26 +2735,26 @@ class App(QtCore.QObject):
                 types = {'outname': str}
 
                 if name is None:
-                    self.raiseTclError('Argument name is missing.')
+                    self.raise_tcl_error('Argument name is missing.')
 
                 for key in kwa:
                     if key not in types:
-                        self.raiseTclError('Unknown parameter: %s' % key)
+                        self.raise_tcl_error('Unknown parameter: %s' % key)
                     try:
                         kwa[key] = types[key](kwa[key])
                     except Exception, e:
-                        self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
+                        self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
 
                 try:
                     obj = self.collection.get_by_name(str(name))
                 except:
-                    self.raiseTclError("Could not retrieve object: %s" % name)
+                    self.raise_tcl_error("Could not retrieve object: %s" % name)
 
                 if obj is None:
-                    self.raiseTclError("Object not found: %s" % name)
+                    self.raise_tcl_error("Object not found: %s" % name)
 
                 if not isinstance(obj, Geometry):
-                    self.raiseTclError('Expected Geometry, got %s %s.' % (name,  type(obj)))
+                    self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
 
                 def geo_init(geo_obj, app_obj):
                     geo_obj.solid_geometry = obj_exteriors
@@ -2768,10 +2768,10 @@ class App(QtCore.QObject):
                     obj_exteriors = obj.get_exteriors()
                     self.new_object('geometry', outname, geo_init)
                 except Exception as e:
-                    self.raiseTclError("Failed: %s" % str(e))
+                    self.raise_tcl_error("Failed: %s" % str(e))
 
             except Exception as unknown:
-                self.raiseTclUnknownError(unknown)
+                self.raise_tcl_unknown_error(unknown)
 
         def interiors(name=None, *args):
             '''
@@ -2787,25 +2787,25 @@ class App(QtCore.QObject):
 
                 for key in kwa:
                     if key not in types:
-                        self.raiseTclError('Unknown parameter: %s' % key)
+                        self.raise_tcl_error('Unknown parameter: %s' % key)
                     try:
                         kwa[key] = types[key](kwa[key])
                     except Exception, e:
-                        self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
+                        self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
 
                 if name is None:
-                    self.raiseTclError('Argument name is missing.')
+                    self.raise_tcl_error('Argument name is missing.')
 
                 try:
                     obj = self.collection.get_by_name(str(name))
                 except:
-                    self.raiseTclError("Could not retrieve object: %s" % name)
+                    self.raise_tcl_error("Could not retrieve object: %s" % name)
 
                 if obj is None:
-                    self.raiseTclError("Object not found: %s" % name)
+                    self.raise_tcl_error("Object not found: %s" % name)
 
                 if not isinstance(obj, Geometry):
-                    self.raiseTclError('Expected Geometry, got %s %s.' % (name,  type(obj)))
+                    self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
 
                 def geo_init(geo_obj, app_obj):
                     geo_obj.solid_geometry = obj_interiors
@@ -2819,10 +2819,10 @@ class App(QtCore.QObject):
                     obj_interiors = obj.get_interiors()
                     self.new_object('geometry', outname, geo_init)
                 except Exception as e:
-                    self.raiseTclError("Failed: %s" % str(e))
+                    self.raise_tcl_error("Failed: %s" % str(e))
 
             except Exception as unknown:
-                self.raiseTclUnknownError(unknown)
+                self.raise_tcl_unknown_error(unknown)
 
         def isolate(name=None, *args):
             '''
@@ -2840,29 +2840,29 @@ class App(QtCore.QObject):
 
             for key in kwa:
                 if key not in types:
-                    self.raiseTclError('Unknown parameter: %s' % key)
+                    self.raise_tcl_error('Unknown parameter: %s' % key)
                 try:
                     kwa[key] = types[key](kwa[key])
                 except Exception, e:
-                    self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
+                    self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
             try:
                 obj = self.collection.get_by_name(str(name))
             except:
-                self.raiseTclError("Could not retrieve object: %s" % name)
+                self.raise_tcl_error("Could not retrieve object: %s" % name)
 
             if obj is None:
-                self.raiseTclError("Object not found: %s" % name)
+                self.raise_tcl_error("Object not found: %s" % name)
 
             assert isinstance(obj, FlatCAMGerber), \
                 "Expected a FlatCAMGerber, got %s" % type(obj)
 
             if not isinstance(obj, FlatCAMGerber):
-                self.raiseTclError('Expected FlatCAMGerber, got %s %s.' % (name,  type(obj)))
+                self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (name, type(obj)))
 
             try:
                 obj.isolate(**kwa)
             except Exception, e:
-                self.raiseTclError("Operation failed: %s" % str(e))
+                self.raise_tcl_error("Operation failed: %s" % str(e))
 
             return 'Ok'
 
@@ -3253,11 +3253,11 @@ class App(QtCore.QObject):
             Test it like this:
             if name is None:
 
-                self.raiseTclError('Argument name is missing.')
+                self.raise_tcl_error('Argument name is missing.')
 
-            When error ocurre, always use raiseTclError, never return "sometext" on error,
+            When error ocurre, always use raise_tcl_error, never return "sometext" on error,
             otherwise we will miss it and processing will silently continue.
-            Method raiseTclError  pass error into TCL interpreter, then raise python exception,
+            Method raise_tcl_error  pass error into TCL interpreter, then raise python exception,
             which is catched in exec_command and displayed in TCL shell console with red background.
             Error in console is displayed  with TCL  trace.
 

+ 20 - 11
FlatCAMObj.py

@@ -1040,6 +1040,10 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
 
         self.app.inform.emit("Saved to: " + filename)
 
+    def get_gcode(self, preamble='', postamble=''):
+        #we need this to beable get_gcode separatelly for shell command export_code
+        return preamble + '\n' + self.gcode + "\n" + postamble
+
     def on_plot_cb_click(self, *args):
         if self.muted_ui:
             return
@@ -1243,7 +1247,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                        outname=None,
                        spindlespeed=None,
                        multidepth=None,
-                       depthperpass=None):
+                       depthperpass=None,
+                       use_thread=True):
         """
         Creates a CNCJob out of this Geometry object. The actual
         work is done by the target FlatCAMCNCjob object's
@@ -1304,18 +1309,22 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
             app_obj.progress.emit(80)
 
-        # To be run in separate thread
-        def job_thread(app_obj):
-            with self.app.proc_container.new("Generating CNC Job."):
-                app_obj.new_object("cncjob", outname, job_init)
-                app_obj.inform.emit("CNCjob created: %s" % outname)
-                app_obj.progress.emit(100)
 
-        # Create a promise with the name
-        self.app.collection.promise(outname)
+        if  use_thread:
+            # To be run in separate thread
+            def job_thread(app_obj):
+                with self.app.proc_container.new("Generating CNC Job."):
+                    app_obj.new_object("cncjob", outname, job_init)
+                    app_obj.inform.emit("CNCjob created: %s" % outname)
+                    app_obj.progress.emit(100)
 
-        # Send to worker
-        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+            # Create a promise with the name
+            self.app.collection.promise(outname)
+
+            # Send to worker
+            self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+        else:
+            self.app.new_object("cncjob", outname, job_init)
 
     def on_plot_cb_click(self, *args):  # TODO: args not needed
         if self.muted_ui:

+ 148 - 42
tclCommands/TclCommand.py

@@ -1,83 +1,108 @@
-import sys, inspect, pkgutil
 import re
 import FlatCAMApp
+import abc
 import collections
+from PyQt4 import QtCore
+from contextlib import contextmanager
+
 
 class TclCommand(object):
 
-    app=None
+    # FlatCAMApp
+    app = None
+
+    # logger
+    log = None
 
     # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
     aliases = []
 
     # dictionary of types from Tcl command, needs to be ordered
+    # OrderedDict should be like collections.OrderedDict([(key,value),(key2,value2)])
     arg_names = collections.OrderedDict([
         ('name', str)
     ])
 
     # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
-    option_types = collections.OrderedDict([])
+    # OrderedDict should be like collections.OrderedDict([(key,value),(key2,value2)])
+    option_types = collections.OrderedDict()
 
     # array of mandatory options for current Tcl command: required = {'name','outname'}
     required = ['name']
 
     # structured help for current command, args needs to be ordered
+    # OrderedDict should be like collections.OrderedDict([(key,value),(key2,value2)])
     help = {
         'main': "undefined help.",
         'args': collections.OrderedDict([
             ('argumentname', 'undefined help.'),
             ('optionname', 'undefined help.')
         ]),
-        'examples' : []
+        'examples': []
     }
 
     def __init__(self, app):
-        self.app=app
+        self.app = app
+        if self.app is None:
+            raise TypeError('Expected app to be FlatCAMApp instance.')
+        if not isinstance(self.app, FlatCAMApp.App):
+            raise TypeError('Expected FlatCAMApp, got %s.' % type(app))
+        self.log = self.app.log
+
+    def raise_tcl_error(self, text):
+        """
+        this method  pass exception from python into TCL as error, so we get stacktrace and reason
+        this is  only redirect to self.app.raise_tcl_error
+        :param text: text of error
+        :return: none
+        """
+
+        self.app.raise_tcl_error(text)
 
     def get_decorated_help(self):
         """
         Decorate help for TCL console output.
 
-        :return: decorated help from structue
+        :return: decorated help from structure
         """
 
-        def get_decorated_command(alias):
+        def get_decorated_command(alias_name):
             command_string = []
-            for key, value in self.help['args'].items():
-                command_string.append(get_decorated_argument(key, value, True))
-            return "> " + alias + " " + " ".join(command_string)
+            for arg_key, arg_type in self.help['args'].items():
+                command_string.append(get_decorated_argument(arg_key, arg_type, True))
+            return "> " + alias_name + " " + " ".join(command_string)
 
-        def get_decorated_argument(key, value, in_command=False):
+        def get_decorated_argument(help_key, help_text, in_command=False):
             option_symbol = ''
-            if key in self.arg_names:
-                type=self.arg_names[key]
-                type_name=str(type.__name__)
+            if help_key in self.arg_names:
+                arg_type = self.arg_names[help_key]
+                type_name = str(arg_type.__name__)
                 in_command_name = "<" + type_name + ">"
-            elif key in self.option_types:
+            elif help_key in self.option_types:
                 option_symbol = '-'
-                type=self.option_types[key]
-                type_name=str(type.__name__)
-                in_command_name = option_symbol + key + " <" + type_name + ">"
+                arg_type = self.option_types[help_key]
+                type_name = str(arg_type.__name__)
+                in_command_name = option_symbol + help_key + " <" + type_name + ">"
             else:
                 option_symbol = ''
-                type_name='?'
-                in_command_name = option_symbol + key + " <" + type_name + ">"
+                type_name = '?'
+                in_command_name = option_symbol + help_key + " <" + type_name + ">"
 
             if in_command:
-                if key in self.required:
+                if help_key in self.required:
                     return in_command_name
                 else:
                     return '[' + in_command_name + "]"
             else:
-                if key in self.required:
-                    return "\t" + option_symbol + key + " <" + type_name + ">: " + value
+                if help_key in self.required:
+                    return "\t" + option_symbol + help_key + " <" + type_name + ">: " + help_text
                 else:
-                    return "\t[" + option_symbol + key + " <" + type_name + ">: " + value+"]"
+                    return "\t[" + option_symbol + help_key + " <" + type_name + ">: " + help_text + "]"
 
-        def get_decorated_example(example):
-            return "> "+example
+        def get_decorated_example(example_item):
+            return "> "+example_item
 
-        help_string=[self.help['main']]
+        help_string = [self.help['main']]
         for alias in self.aliases:
             help_string.append(get_decorated_command(alias))
 
@@ -89,12 +114,17 @@ class TclCommand(object):
 
         return "\n".join(help_string)
 
-    def parse_arguments(self, args):
+    @staticmethod
+    def parse_arguments(args):
             """
             Pre-processes arguments to detect '-keyword value' pairs into dictionary
             and standalone parameters into list.
 
-            This is copy from FlatCAMApp.setup_shell().h() just for accesibility,  original should  be removed  after all commands will be converted
+            This is copy from FlatCAMApp.setup_shell().h() just for accessibility,
+            original should  be removed  after all commands will be converted
+
+            :param args: arguments from tcl to parse
+            :return: arguments, options
             """
 
             options = {}
@@ -121,41 +151,43 @@ class TclCommand(object):
         Check arguments and  options for right types
 
         :param args: arguments from tcl to check
-        :return:
+        :return: named_args, unnamed_args
         """
 
         arguments, options = self.parse_arguments(args)
 
-        named_args={}
-        unnamed_args=[]
+        named_args = {}
+        unnamed_args = []
 
         # check arguments
-        idx=0
-        arg_names_items=self.arg_names.items()
+        idx = 0
+        arg_names_items = self.arg_names.items()
         for argument in arguments:
             if len(self.arg_names) > idx:
-                key, type = arg_names_items[idx]
+                key, arg_type = arg_names_items[idx]
                 try:
-                    named_args[key] = type(argument)
+                    named_args[key] = arg_type(argument)
                 except Exception, e:
-                    self.app.raiseTclError("Cannot cast named argument '%s' to type %s." % (key, type))
+                    self.raise_tcl_error("Cannot cast named argument '%s' to type %s  with exception '%s'."
+                                         % (key, arg_type, str(e)))
             else:
                 unnamed_args.append(argument)
             idx += 1
 
-        # check otions
+        # check options
         for key in options:
             if key not in self.option_types:
-                self.app.raiseTclError('Unknown parameter: %s' % key)
+                self.raise_tcl_error('Unknown parameter: %s' % key)
             try:
                 named_args[key] = self.option_types[key](options[key])
             except Exception, e:
-                self.app.raiseTclError("Cannot cast argument '-%s' to type %s." % (key, self.option_types[key]))
+                self.raise_tcl_error("Cannot cast argument '-%s' to type '%s' with exception '%s'."
+                                     % (key, self.option_types[key], str(e)))
 
         # check required arguments
         for key in self.required:
             if key not in named_args:
-                self.app.raiseTclError("Missing required argument '%s'." % (key))
+                self.raise_tcl_error("Missing required argument '%s'." % key)
 
         return named_args, unnamed_args
 
@@ -168,12 +200,16 @@ class TclCommand(object):
         :param args: arguments passed from tcl command console
         :return: None, output text or exception
         """
+
         try:
+            self.log.debug("TCL command '%s' executed." % str(self.__class__))
             args, unnamed_args = self.check_args(args)
             return self.execute(args, unnamed_args)
         except Exception as unknown:
-            self.app.raiseTclUnknownError(unknown)
+            self.log.error("TCL command '%s' failed." % str(self))
+            self.app.raise_tcl_unknown_error(unknown)
 
+    @abc.abstractmethod
     def execute(self, args, unnamed_args):
         """
         Direct execute of command, this method should be implemented in each descendant.
@@ -186,3 +222,73 @@ class TclCommand(object):
         """
 
         raise NotImplementedError("Please Implement this method")
+
+
+class TclCommandSignaled(TclCommand):
+    """
+        !!! I left it here only  for demonstration !!!
+        Go to TclCommandCncjob and  into class definition put
+            class TclCommandCncjob(TclCommand.TclCommandSignaled):
+        also change
+            obj.generatecncjob(use_thread = False, **args)
+        to
+            obj.generatecncjob(use_thread = True, **args)
+
+
+        This class is  child of  TclCommand and is used for commands  which create  new objects
+        it handles  all neccessary stuff about blocking and passing exeptions
+    """
+
+    # default  timeout for operation is  30 sec, but it can be much more
+    default_timeout = 30000
+
+
+    def execute_wrapper(self, *args):
+        """
+        Command which is called by tcl console when current commands aliases are hit.
+        Main catch(except) is implemented here.
+        This method should be reimplemented only when initial checking sequence differs
+
+        :param args: arguments passed from tcl command console
+        :return: None, output text or exception
+        """
+
+        @contextmanager
+        def wait_signal(signal, timeout=30000):
+            """Block loop until signal emitted, or timeout (ms) elapses."""
+            loop = QtCore.QEventLoop()
+            signal.connect(loop.quit)
+
+            status = {'timed_out': False}
+
+            def report_quit():
+                status['timed_out'] = True
+                loop.quit()
+
+            yield
+
+            if timeout is not None:
+                QtCore.QTimer.singleShot(timeout, report_quit)
+
+            loop.exec_()
+
+            if status['timed_out']:
+                self.app.raise_tcl_unknown_error('Operation timed out!')
+
+        try:
+            self.log.debug("TCL command '%s' executed." % str(self.__class__))
+            args, unnamed_args = self.check_args(args)
+            if 'timeout' in args:
+                passed_timeout=args['timeout']
+                del args['timeout']
+            else:
+                passed_timeout=self.default_timeout
+            with wait_signal(self.app.new_object_available, passed_timeout):
+                # every TclCommandNewObject ancestor  support  timeout as parameter,
+                # but it does not mean anything for child itself
+                # when operation  will be  really long is good  to set it higher then defqault 30s
+                return self.execute(args, unnamed_args)
+
+        except Exception as unknown:
+            self.log.error("TCL command '%s' failed." % str(self))
+            self.app.raise_tcl_unknown_error(unknown)

+ 8 - 12
tclCommands/TclCommandAddPolygon.py

@@ -8,7 +8,7 @@ class TclCommandAddPolygon(TclCommand.TclCommand):
     """
 
     # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
-    aliases = ['add_polygon','add_poly']
+    aliases = ['add_polygon', 'add_poly']
 
     # dictionary of types from Tcl command, needs to be ordered
     arg_names = collections.OrderedDict([
@@ -16,7 +16,7 @@ class TclCommandAddPolygon(TclCommand.TclCommand):
     ])
 
     # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
-    option_types = collections.OrderedDict([])
+    option_types = collections.OrderedDict()
 
     # array of mandatory options for current Tcl command: required = {'name','outname'}
     required = ['name']
@@ -28,7 +28,7 @@ class TclCommandAddPolygon(TclCommand.TclCommand):
             ('name', 'Name of the Geometry object to which to append the polygon.'),
             ('xi, yi', 'Coordinates of points in the polygon.')
         ]),
-        'examples':[
+        'examples': [
             'add_polygon <name> <x0> <y0> <x1> <y1> <x2> <y2> [x3 y3 [...]]'
         ]
     }
@@ -45,21 +45,17 @@ class TclCommandAddPolygon(TclCommand.TclCommand):
 
         name = args['name']
 
-        try:
-            obj = self.app.collection.get_by_name(name)
-        except:
-            self.app.raiseTclError("Could not retrieve object: %s" % name)
-
+        obj = self.app.collection.get_by_name(name)
         if obj is None:
-            self.app.raiseTclError("Object not found: %s" % name)
+            self.raise_tcl_error("Object not found: %s" % name)
 
         if not isinstance(obj, Geometry):
-            self.app.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
+            self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
 
         if len(unnamed_args) % 2 != 0:
-            self.app.raiseTclError("Incomplete coordinates.")
+            self.raise_tcl_error("Incomplete coordinates.")
 
         points = [[float(unnamed_args[2*i]), float(unnamed_args[2*i+1])] for i in range(len(unnamed_args)/2)]
 
         obj.add_polygon(points)
-        obj.plot()
+        obj.plot()

+ 7 - 11
tclCommands/TclCommandAddPolyline.py

@@ -16,7 +16,7 @@ class TclCommandAddPolyline(TclCommand.TclCommand):
     ])
 
     # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
-    option_types = collections.OrderedDict([])
+    option_types = collections.OrderedDict()
 
     # array of mandatory options for current Tcl command: required = {'name','outname'}
     required = ['name']
@@ -28,7 +28,7 @@ class TclCommandAddPolyline(TclCommand.TclCommand):
             ('name', 'Name of the Geometry object to which to append the polyline.'),
             ('xi, yi', 'Coordinates of points in the polyline.')
         ]),
-        'examples':[
+        'examples': [
             'add_polyline <name> <x0> <y0> <x1> <y1> <x2> <y2> [x3 y3 [...]]'
         ]
     }
@@ -45,21 +45,17 @@ class TclCommandAddPolyline(TclCommand.TclCommand):
 
         name = args['name']
 
-        try:
-            obj = self.app.collection.get_by_name(name)
-        except:
-            self.app.raiseTclError("Could not retrieve object: %s" % name)
-
+        obj = self.app.collection.get_by_name(name)
         if obj is None:
-            self.app.raiseTclError("Object not found: %s" % name)
+            self.raise_tcl_error("Object not found: %s" % name)
 
         if not isinstance(obj, Geometry):
-            self.app.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
+            self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
 
         if len(unnamed_args) % 2 != 0:
-            self.app.raiseTclError("Incomplete coordinates.")
+            self.raise_tcl_error("Incomplete coordinates.")
 
         points = [[float(unnamed_args[2*i]), float(unnamed_args[2*i+1])] for i in range(len(unnamed_args)/2)]
 
         obj.add_polyline(points)
-        obj.plot()
+        obj.plot()

+ 86 - 0
tclCommands/TclCommandCncjob.py

@@ -0,0 +1,86 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandCncjob(TclCommand.TclCommand):
+    """
+    Tcl shell command to Generates a CNC Job from a Geometry Object.
+
+    example:
+        set_sys units MM
+        new
+        open_gerber tests/gerber_files/simple1.gbr -outname margin
+        isolate margin -dia 3
+        cncjob margin_iso
+    """
+
+    # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
+    aliases = ['cncjob']
+
+    # dictionary of types from Tcl command, needs to be ordered
+    arg_names = collections.OrderedDict([
+        ('name', str)
+    ])
+
+    # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
+    option_types = collections.OrderedDict([
+        ('z_cut',float),
+        ('z_move',float),
+        ('feedrate',float),
+        ('tooldia',float),
+        ('spindlespeed',int),
+        ('multidepth',bool),
+        ('depthperpass',float),
+        ('outname',str)
+    ])
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = ['name']
+
+    # structured help for current command, args needs to be ordered
+    help = {
+        'main': "Generates a CNC Job from a Geometry Object.",
+        'args': collections.OrderedDict([
+            ('name', 'Name of the source object.'),
+            ('z_cut', 'Z-axis cutting position.'),
+            ('z_move', 'Z-axis moving position.'),
+            ('feedrate', 'Moving speed when cutting.'),
+            ('tooldia', 'Tool diameter to show on screen.'),
+            ('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'),
+            ('multidepth', 'Use or not multidepth cnccut.'),
+            ('depthperpass', 'Height of one layer for multidepth.'),
+            ('outname', 'Name of the resulting Geometry object.'),
+            ('timeout', 'Max wait for job timeout before error.')
+        ]),
+        'examples': []
+    }
+
+    def execute(self, args, unnamed_args):
+        """
+        execute current TCL shell command
+
+        :param args: array of known named arguments and options
+        :param unnamed_args: array of other values which were passed into command
+            without -somename and  we do not have them in known arg_names
+        :return: None or exception
+        """
+
+        name = args['name']
+
+        if 'outname' not in args:
+            args['outname'] = name + "_cnc"
+
+        if 'timeout' in args:
+            timeout = args['timeout']
+        else:
+            timeout = 10000
+
+        obj = self.app.collection.get_by_name(name)
+        if obj is None:
+            self.raise_tcl_error("Object not found: %s" % name)
+
+        if not isinstance(obj, FlatCAMGeometry):
+            self.raise_tcl_error('Expected FlatCAMGeometry, got %s %s.' % (name, type(obj)))
+
+        del args['name']
+        obj.generatecncjob(use_thread = False, **args)

+ 79 - 0
tclCommands/TclCommandExportGcode.py

@@ -0,0 +1,79 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandExportGcode(TclCommand.TclCommand):
+    """
+    Tcl shell command to export gcode as  tcl output for "set X [export_gcode ...]"
+
+    Requires name to be available. It might still be in the
+    making at the time this function is called, so check for
+    promises and send to background if there are promises.
+
+
+    this  export   may be  catched   by tcl and past as preable  to another  export_gcode or write_gcode
+    this can be used to join GCODES
+
+    example:
+        set_sys units MM
+        new
+        open_gerber tests/gerber_files/simple1.gbr -outname margin
+        isolate margin -dia 3
+        cncjob margin_iso
+        cncjob margin_iso
+        set EXPORT [export_gcode margin_iso_cnc]
+        write_gcode margin_iso_cnc_1 /tmp/file.gcode ${EXPORT}
+
+    """
+
+    # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
+    aliases = ['export_gcode']
+
+    # dictionary of types from Tcl command, needs to be ordered
+    arg_names = collections.OrderedDict([
+        ('name', str),
+        ('preamble', str),
+        ('postamble', str)
+    ])
+
+    # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
+    option_types = collections.OrderedDict()
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = ['name']
+
+    # structured help for current command, args needs to be ordered
+    help = {
+        'main': "Export gcode into console output.",
+        'args': collections.OrderedDict([
+            ('name', 'Name of the source Geometry object.'),
+            ('preamble', 'Prepend GCODE.'),
+            ('postamble', 'Append GCODE.')
+        ]),
+        'examples': []
+    }
+
+    def execute(self, args, unnamed_args):
+        """
+        execute current TCL shell command
+
+        :param args: array of known named arguments and options
+        :param unnamed_args: array of other values which were passed into command
+            without -somename and  we do not have them in known arg_names
+        :return: None or exception
+        """
+
+        name = args['name']
+
+        obj = self.app.collection.get_by_name(name)
+        if obj is None:
+            self.raise_tcl_error("Object not found: %s" % name)
+
+        if not isinstance(obj, CNCjob):
+            self.raise_tcl_error('Expected CNCjob, got %s %s.' % (name, type(obj)))
+
+        if self.app.collection.has_promises():
+            self.raise_tcl_error('!!!Promises exists, but should not here!!!')
+
+        del args['name']
+        return obj.get_gcode(**args)

+ 7 - 11
tclCommands/TclCommandExteriors.py

@@ -8,7 +8,7 @@ class TclCommandExteriors(TclCommand.TclCommand):
     """
 
     # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
-    aliases = ['exteriors','ext']
+    aliases = ['exteriors', 'ext']
 
     # dictionary of types from Tcl command, needs to be ordered
     arg_names = collections.OrderedDict([
@@ -30,7 +30,7 @@ class TclCommandExteriors(TclCommand.TclCommand):
             ('name', 'Name of the source Geometry object.'),
             ('outname', 'Name of the resulting Geometry object.')
         ]),
-        'examples':[]
+        'examples': []
     }
 
     def execute(self, args, unnamed_args):
@@ -50,19 +50,15 @@ class TclCommandExteriors(TclCommand.TclCommand):
         else:
             outname = name + "_exteriors"
 
-        try:
-            obj = self.app.collection.get_by_name(name)
-        except:
-            self.app.raiseTclError("Could not retrieve object: %s" % name)
-
+        obj = self.app.collection.get_by_name(name)
         if obj is None:
-            self.app.raiseTclError("Object not found: %s" % name)
+            self.raise_tcl_error("Object not found: %s" % name)
 
         if not isinstance(obj, Geometry):
-            self.app.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
+            self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
 
-        def geo_init(geo_obj, app_obj):
+        def geo_init(geo_obj):
             geo_obj.solid_geometry = obj_exteriors
 
         obj_exteriors = obj.get_exteriors()
-        self.app.new_object('geometry', outname, geo_init)
+        self.app.new_object('geometry', outname, geo_init)

+ 7 - 10
tclCommands/TclCommandInteriors.py

@@ -1,6 +1,7 @@
 from ObjectCollection import *
 import TclCommand
 
+
 class TclCommandInteriors(TclCommand.TclCommand):
     """
     Tcl shell command to get interiors of polygons
@@ -29,7 +30,7 @@ class TclCommandInteriors(TclCommand.TclCommand):
             ('name', 'Name of the source Geometry object.'),
             ('outname', 'Name of the resulting Geometry object.')
         ]),
-        'examples':[]
+        'examples': []
     }
 
     def execute(self, args, unnamed_args):
@@ -49,19 +50,15 @@ class TclCommandInteriors(TclCommand.TclCommand):
         else:
             outname = name + "_interiors"
 
-        try:
-            obj = self.app.collection.get_by_name(name)
-        except:
-            self.app.raiseTclError("Could not retrieve object: %s" % name)
-
+        obj = self.app.collection.get_by_name(name)
         if obj is None:
-            self.app.raiseTclError("Object not found: %s" % name)
+            self.raise_tcl_error("Object not found: %s" % name)
 
         if not isinstance(obj, Geometry):
-            self.app.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
+            self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
 
-        def geo_init(geo_obj, app_obj):
+        def geo_init(geo_obj):
             geo_obj.solid_geometry = obj_exteriors
 
         obj_exteriors = obj.get_interiors()
-        self.app.new_object('geometry', outname, geo_init)
+        self.app.new_object('geometry', outname, geo_init)

+ 15 - 15
tclCommands/__init__.py

@@ -1,5 +1,4 @@
 import pkgutil
-import inspect
 import sys
 
 # allowed command modules
@@ -7,9 +6,10 @@ import tclCommands.TclCommandExteriors
 import tclCommands.TclCommandInteriors
 import tclCommands.TclCommandAddPolygon
 import tclCommands.TclCommandAddPolyline
+import tclCommands.TclCommandExportGcode
+import tclCommands.TclCommandCncjob
 
-
-__all__=[]
+__all__ = []
 
 for loader, name, is_pkg in pkgutil.walk_packages(__path__):
     module = loader.find_module(name).load_module(name)
@@ -25,8 +25,8 @@ def register_all_commands(app, commands):
 
     we need import all  modules  in top section:
     import tclCommands.TclCommandExteriors
-    at this stage we can include only wanted  commands  with this, autoloading may be implemented in future
-    I have no enought knowledge about python's anatomy. Would be nice to include all classes which are descendant etc.
+    at this stage we can include only wanted  commands  with this, auto loading may be implemented in future
+    I have no enough knowledge about python's anatomy. Would be nice to include all classes which are descendant etc.
 
     :param app: FlatCAMApp
     :param commands: array of commands  which should be modified
@@ -35,14 +35,14 @@ def register_all_commands(app, commands):
 
     tcl_modules = {k: v for k, v in sys.modules.items() if k.startswith('tclCommands.TclCommand')}
 
-    for key, module in tcl_modules.items():
+    for key, mod in tcl_modules.items():
         if key != 'tclCommands.TclCommand':
-            classname = key.split('.')[1]
-            class_ = getattr(module, classname)
-            commandInstance=class_(app)
-
-            for alias in commandInstance.aliases:
-                commands[alias]={
-                    'fcn': commandInstance.execute_wrapper,
-                    'help': commandInstance.get_decorated_help()
-                }
+            class_name = key.split('.')[1]
+            class_type = getattr(mod, class_name)
+            command_instance = class_type(app)
+
+            for alias in command_instance.aliases:
+                commands[alias] = {
+                    'fcn': command_instance.execute_wrapper,
+                    'help': command_instance.get_decorated_help()
+                }