Przeglądaj źródła

draft for reimplementation of tcl commands to separated files/modules

Kamil Sopko 10 lat temu
rodzic
commit
cd6700152c

+ 6 - 1
FlatCAMApp.py

@@ -25,7 +25,7 @@ from FlatCAMDraw import FlatCAMDraw
 from FlatCAMProcess import *
 from MeasurementTool import Measurement
 from DblSidedTool import DblSidedTool
-
+import tclCommands
 
 ########################################
 ##                App                 ##
@@ -660,6 +660,8 @@ class App(QtCore.QObject):
 
         if not isinstance(unknownException, self.TclErrorException):
             self.raiseTclError("Unknown error: %s" % str(unknownException))
+        else:
+            raise unknownException
 
     def raiseTclError(self, text):
         """
@@ -3547,6 +3549,9 @@ class App(QtCore.QObject):
             }
         }
 
+        #import/overwrite tcl commands as objects of TclCommand descendants
+        tclCommands.register_all_commands(self, commands)
+
         # Add commands to the tcl interpreter
         for cmd in commands:
             self.tcl.createcommand(cmd, commands[cmd]['fcn'])

+ 180 - 0
tclCommands/TclCommand.py

@@ -0,0 +1,180 @@
+import sys, inspect, pkgutil
+import re
+import FlatCAMApp
+
+
+class TclCommand(object):
+
+    app=None
+
+    # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
+    aliases = None
+
+    # dictionary of types from Tcl command: args = {'name': str}, this is  for  value without optionname
+    arg_names = {'name': str}
+
+    # dictionary of types from Tcl command: types = {'outname': str} , this  is  for options  like -optionname value
+    option_types = {}
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = ['name']
+
+    # structured help for current command
+    help = {
+        'main': "undefined help.",
+        'args': {
+            'argumentname': 'undefined help.',
+            'optionname': 'undefined help.'
+        },
+        'examples' : []
+    }
+
+    def __init__(self, app):
+        self.app=app
+
+    def get_decorated_help(self):
+        """
+        Decorate help for TCL console output.
+
+        :return: decorated help from structue
+        """
+
+        def get_decorated_command(alias):
+            command_string = []
+            for key, value in reversed(self.help['args'].items()):
+                command_string.append(get_decorated_argument(key, value, True))
+            return "> " + alias + " " + " ".join(command_string)
+
+        def get_decorated_argument(key, value, in_command=False):
+            option_symbol = ''
+            if key in self.arg_names:
+                type=self.arg_names[key]
+                in_command_name = "<" + str(type.__name__) + ">"
+            else:
+                option_symbol = '-'
+                type=self.option_types[key]
+                in_command_name = option_symbol + key + " <" + str(type.__name__) + ">"
+            if in_command:
+                if key in self.required:
+                    return in_command_name
+                else:
+                    return '[' + in_command_name + "]"
+            else:
+                if key in self.required:
+                    return "\t" + option_symbol + key + " <" + str(type.__name__) + ">: " + value
+                else:
+                    return "\t[" + option_symbol + key + " <" + str(type.__name__) + ">: " + value+"]"
+
+        def get_decorated_example(example):
+            example_string = ''
+            return "todo" + example_string
+
+        help_string=[self.help['main']]
+        for alias in self.aliases:
+            help_string.append(get_decorated_command(alias))
+
+        for key, value in reversed(self.help['args'].items()):
+            help_string.append(get_decorated_argument(key, value))
+
+        for example in self.help['examples']:
+            help_string.append(get_decorated_example(example))
+
+        return "\n".join(help_string)
+
+    def parse_arguments(self, 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
+            """
+
+            options = {}
+            arguments = []
+            n = len(args)
+            name = None
+            for i in range(n):
+                match = re.search(r'^-([a-zA-Z].*)', args[i])
+                if match:
+                    assert name is None
+                    name = match.group(1)
+                    continue
+
+                if name is None:
+                    arguments.append(args[i])
+                else:
+                    options[name] = args[i]
+                    name = None
+
+            return arguments, options
+
+    def check_args(self, args):
+        """
+        Check arguments and  options for right types
+
+        :param args: arguments from tcl to check
+        :return:
+        """
+
+        arguments, options = self.parse_arguments(args)
+
+        named_args={}
+        unnamed_args=[]
+
+        # check arguments
+        idx=0
+        arg_names_reversed=self.arg_names.items()
+        for argument in arguments:
+            if len(self.arg_names) > idx:
+                key, type = arg_names_reversed[len(self.arg_names)-idx-1]
+                try:
+                    named_args[key] = type(argument)
+                except Exception, e:
+                    self.app.raiseTclError("Cannot cast named argument '%s' to type %s." % (key, type))
+            else:
+                unnamed_args.append(argument)
+            idx += 1
+
+        # check otions
+        for key in options:
+            if key not in self.option_types:
+                self.app.raiseTclError('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]))
+
+        # check required arguments
+        for key in self.required:
+            if key not in named_args:
+                self.app.raiseTclError("Missing required argument '%s'." % (key))
+
+        return named_args, unnamed_args
+
+    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
+        """
+        try:
+            args, unnamed_args = self.check_args(args)
+            return self.execute(args, unnamed_args)
+        except Exception as unknown:
+            self.app.raiseTclUnknownError(unknown)
+
+    def execute(self, args, unnamed_args):
+        """
+        Direct execute of command, this method should be implemented in each descendant.
+        No main catch should be implemented here.
+
+        :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, output text or exception
+        """
+
+        raise NotImplementedError("Please Implement this method")

+ 64 - 0
tclCommands/TclCommandExteriors.py

@@ -0,0 +1,64 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandExteriors(TclCommand.TclCommand):
+    """
+    Tcl shell command to get exteriors of polygons
+    """
+
+    # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
+    aliases = ['exteriors','ext']
+
+    # dictionary of types from Tcl command: args = {'name': str}, this is  for  value without optionname
+    arg_names = {'name': str,'name2': str}
+
+    # dictionary of types from Tcl command: types = {'outname': str} , this  is  for options  like -optionname value
+    option_types = {'outname': str}
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = ['name']
+
+    # structured help for current command
+    help = {
+        'main': "Get exteriors of polygons.",
+        'args': {
+            'name': 'Name of the source Geometry object.',
+            'outname': 'Name of the resulting Geometry object.'
+        },
+        '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' in args:
+            outname = args['outname']
+        else:
+            outname = name + "_exteriors"
+
+        try:
+            obj = self.app.collection.get_by_name(name)
+        except:
+            self.app.raiseTclError("Could not retrieve object: %s" % name)
+
+        if obj is None:
+            self.app.raiseTclError("Object not found: %s" % name)
+
+        if not isinstance(obj, Geometry):
+            self.app.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
+
+        def geo_init(geo_obj, app_obj):
+            geo_obj.solid_geometry = obj_exteriors
+
+        obj_exteriors = obj.get_exteriors()
+        self.app.new_object('geometry', outname, geo_init)

+ 63 - 0
tclCommands/TclCommandInteriors.py

@@ -0,0 +1,63 @@
+from ObjectCollection import *
+import TclCommand
+
+class TclCommandInteriors(TclCommand.TclCommand):
+    """
+    Tcl shell command to get interiors of polygons
+    """
+
+    # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
+    aliases = ['interiors']
+
+    # dictionary of types from Tcl command: args = {'name': str}, this is  for  value without optionname
+    arg_names = {'name': str}
+
+    # dictionary of types from Tcl command: types = {'outname': str} , this  is  for options  like -optionname value
+    option_types = {'outname': str}
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = ['name']
+
+    # structured help for current command
+    help = {
+        'main': "Get interiors of polygons.",
+        'args': {
+            'name': 'Name of the source Geometry object.',
+            'outname': 'Name of the resulting Geometry object.'
+        },
+        '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' in args:
+            outname = args['outname']
+        else:
+            outname = name + "_interiors"
+
+        try:
+            obj = self.app.collection.get_by_name(name)
+        except:
+            self.app.raiseTclError("Could not retrieve object: %s" % name)
+
+        if obj is None:
+            self.app.raiseTclError("Object not found: %s" % name)
+
+        if not isinstance(obj, Geometry):
+            self.app.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
+
+        def geo_init(geo_obj, app_obj):
+            geo_obj.solid_geometry = obj_exteriors
+
+        obj_exteriors = obj.get_interiors()
+        self.app.new_object('geometry', outname, geo_init)

+ 46 - 0
tclCommands/__init__.py

@@ -0,0 +1,46 @@
+import pkgutil
+import inspect
+import sys
+
+# allowed command modules
+import tclCommands.TclCommandExteriors
+import tclCommands.TclCommandInteriors
+
+
+__all__=[]
+
+for loader, name, is_pkg in pkgutil.walk_packages(__path__):
+    module = loader.find_module(name).load_module(name)
+    __all__.append(name)
+
+
+def register_all_commands(app, commands):
+    """
+    Static method which register all known commands.
+
+    Command should  be for now in directory tclCommands and module should start with TCLCommand
+    Class  have to follow same  name as module.
+
+    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.
+
+    :param app: FlatCAMApp
+    :param commands: array of commands  which should be modified
+    :return: None
+    """
+
+    tcl_modules = {k: v for k, v in sys.modules.items() if k.startswith('tclCommands.TclCommand')}
+
+    for key, module 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()
+                }