draft for reimplementation of tcl commands to separated files/modules
This commit is contained in:
parent
4df46df19b
commit
cd6700152c
|
@ -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'])
|
||||
|
|
|
@ -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")
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
||||
}
|
Loading…
Reference in New Issue