draft for reimplementation of tcl commands to separated files/modules

This commit is contained in:
Kamil Sopko 2016-03-16 18:57:43 +01:00
parent 4df46df19b
commit cd6700152c
5 changed files with 359 additions and 1 deletions

View File

@ -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
tclCommands/TclCommand.py Normal file
View File

@ -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")

View File

@ -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)

View File

@ -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
tclCommands/__init__.py Normal file
View File

@ -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()
}