From 980638630d53ead63f9c1952be74eb9ccaa2743c Mon Sep 17 00:00:00 2001 From: Kamil Sopko Date: Sat, 19 Mar 2016 15:13:07 +0100 Subject: [PATCH] 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 --- FlatCAMApp.py | 102 +++++++------- FlatCAMObj.py | 31 +++-- tclCommands/TclCommand.py | 190 +++++++++++++++++++++------ tclCommands/TclCommandAddPolygon.py | 20 ++- tclCommands/TclCommandAddPolyline.py | 18 +-- tclCommands/TclCommandCncjob.py | 86 ++++++++++++ tclCommands/TclCommandExportGcode.py | 79 +++++++++++ tclCommands/TclCommandExteriors.py | 18 +-- tclCommands/TclCommandInteriors.py | 17 +-- tclCommands/__init__.py | 28 ++-- 10 files changed, 427 insertions(+), 162 deletions(-) create mode 100644 tclCommands/TclCommandCncjob.py create mode 100644 tclCommands/TclCommandExportGcode.py diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 0090ad02..840cf425 100644 --- a/FlatCAMApp.py +++ b/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. diff --git a/FlatCAMObj.py b/FlatCAMObj.py index b8315fc5..b4756920 100644 --- a/FlatCAMObj.py +++ b/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: diff --git a/tclCommands/TclCommand.py b/tclCommands/TclCommand.py index eb8e222a..7f8a7e8d 100644 --- a/tclCommands/TclCommand.py +++ b/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) \ No newline at end of file diff --git a/tclCommands/TclCommandAddPolygon.py b/tclCommands/TclCommandAddPolygon.py index b5effcd8..6d2c2afd 100644 --- a/tclCommands/TclCommandAddPolygon.py +++ b/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 [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() \ No newline at end of file + obj.plot() diff --git a/tclCommands/TclCommandAddPolyline.py b/tclCommands/TclCommandAddPolyline.py index 157f6e1f..57b8fe02 100644 --- a/tclCommands/TclCommandAddPolyline.py +++ b/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 [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() \ No newline at end of file + obj.plot() diff --git a/tclCommands/TclCommandCncjob.py b/tclCommands/TclCommandCncjob.py new file mode 100644 index 00000000..17a677ee --- /dev/null +++ b/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) \ No newline at end of file diff --git a/tclCommands/TclCommandExportGcode.py b/tclCommands/TclCommandExportGcode.py new file mode 100644 index 00000000..520f6ecb --- /dev/null +++ b/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) diff --git a/tclCommands/TclCommandExteriors.py b/tclCommands/TclCommandExteriors.py index d445cd5c..16f2fee4 100644 --- a/tclCommands/TclCommandExteriors.py +++ b/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) \ No newline at end of file + self.app.new_object('geometry', outname, geo_init) diff --git a/tclCommands/TclCommandInteriors.py b/tclCommands/TclCommandInteriors.py index ef67ce9c..2314be3f 100644 --- a/tclCommands/TclCommandInteriors.py +++ b/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) \ No newline at end of file + self.app.new_object('geometry', outname, geo_init) diff --git a/tclCommands/__init__.py b/tclCommands/__init__.py index 45d0ffcf..3055dbc7 100644 --- a/tclCommands/__init__.py +++ b/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) + class_name = key.split('.')[1] + class_type = getattr(mod, class_name) + command_instance = class_type(app) - for alias in commandInstance.aliases: - commands[alias]={ - 'fcn': commandInstance.execute_wrapper, - 'help': commandInstance.get_decorated_help() - } \ No newline at end of file + for alias in command_instance.aliases: + commands[alias] = { + 'fcn': command_instance.execute_wrapper, + 'help': command_instance.get_decorated_help() + }