Merged in sopak/flatcam/tcl-commands (pull request #38)
Tcl commands error handling fix
This commit is contained in:
commit
841a45e145
@ -1,4 +1,4 @@
|
||||
import sys
|
||||
import sys, traceback
|
||||
import urllib
|
||||
import getopt
|
||||
import random
|
||||
@ -286,6 +286,8 @@ class App(QtCore.QObject):
|
||||
"cncjob_tooldia": 0.016,
|
||||
"cncjob_prepend": "",
|
||||
"cncjob_append": "",
|
||||
"background_timeout": 300000, #default value is 5 minutes
|
||||
"verbose_error_level": 0, # shell verbosity 0 = default(python trace only for unknown errors), 1 = show trace(show trace allways), 2 = (For the future).
|
||||
|
||||
# Persistence
|
||||
"last_folder": None,
|
||||
@ -679,12 +681,12 @@ class App(QtCore.QObject):
|
||||
"""
|
||||
this exception is deffined here, to be able catch it if we sucessfully handle all errors from shell command
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def raise_tcl_unknown_error(self, unknownException):
|
||||
"""
|
||||
raise Exception if is different type than TclErrorException
|
||||
this is here mainly to show unknown errors inside TCL shell console
|
||||
:param unknownException:
|
||||
:return:
|
||||
"""
|
||||
@ -694,6 +696,40 @@ class App(QtCore.QObject):
|
||||
else:
|
||||
raise unknownException
|
||||
|
||||
def display_tcl_error(self, error, error_info=None):
|
||||
"""
|
||||
escape bracket [ with \ otherwise there is error
|
||||
"ERROR: missing close-bracket" instead of real error
|
||||
:param error: it may be text or exception
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if isinstance(error, Exception):
|
||||
|
||||
exc_type, exc_value, exc_traceback = error_info
|
||||
if not isinstance(error, self.TclErrorException):
|
||||
show_trace = 1
|
||||
else:
|
||||
show_trace = int(self.defaults['verbose_error_level'])
|
||||
|
||||
if show_trace > 0:
|
||||
trc=traceback.format_list(traceback.extract_tb(exc_traceback))
|
||||
trc_formated=[]
|
||||
for a in reversed(trc):
|
||||
trc_formated.append(a.replace(" ", " > ").replace("\n",""))
|
||||
text="%s\nPython traceback: %s\n%s" % (exc_value,
|
||||
exc_type,
|
||||
"\n".join(trc_formated))
|
||||
|
||||
else:
|
||||
text="%s" % error
|
||||
else:
|
||||
text=error
|
||||
|
||||
text = text.replace('[', '\\[').replace('"','\\"')
|
||||
|
||||
self.tcl.eval('return -code error "%s"' % text)
|
||||
|
||||
def raise_tcl_error(self, text):
|
||||
"""
|
||||
this method pass exception from python into TCL as error, so we get stacktrace and reason
|
||||
@ -701,7 +737,7 @@ class App(QtCore.QObject):
|
||||
:return: raise exception
|
||||
"""
|
||||
|
||||
self.tcl.eval('return -code error "%s"' % text)
|
||||
self.display_tcl_error(text)
|
||||
raise self.TclErrorException(text)
|
||||
|
||||
def exec_command(self, text):
|
||||
|
@ -2777,7 +2777,7 @@ class CNCjob(Geometry):
|
||||
# so we actually are sorting the tools by diameter
|
||||
sorted_tools = sorted(exobj.tools.items(), key = lambda x: x[1])
|
||||
if tools == "all":
|
||||
tools = str([i[0] for i in sorted_tools]) # we get a string of ordered tools
|
||||
tools = [i[0] for i in sorted_tools] # we get a array of ordered tools
|
||||
log.debug("Tools 'all' and sorted are: %s" % str(tools))
|
||||
else:
|
||||
selected_tools = [x.strip() for x in tools.split(",")] # we strip spaces and also separate the tools by ','
|
||||
@ -2818,6 +2818,8 @@ class CNCjob(Geometry):
|
||||
|
||||
for tool in tools:
|
||||
|
||||
# only if tool have some points, otherwise thre may be error and this part is useless
|
||||
if tool in points:
|
||||
# Tool change sequence (optional)
|
||||
if toolchange:
|
||||
gcode += "G00 Z%.4f\n" % toolchangez
|
||||
|
@ -125,6 +125,10 @@ class TclCommand(object):
|
||||
for key, value in self.help['args'].items():
|
||||
help_string.append(get_decorated_argument(key, value))
|
||||
|
||||
# timeout is unique for signaled commands (this is not best oop practice, but much easier for now)
|
||||
if isinstance(self, TclCommandSignaled):
|
||||
help_string.append("\t[-timeout <int>: Max wait for job timeout before error.]")
|
||||
|
||||
for example in self.help['examples']:
|
||||
help_string.append(get_decorated_example(example))
|
||||
|
||||
@ -192,10 +196,13 @@ class TclCommand(object):
|
||||
|
||||
# check options
|
||||
for key in options:
|
||||
if key not in self.option_types and key is not 'timeout':
|
||||
if key not in self.option_types and key != 'timeout':
|
||||
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||
try:
|
||||
if key != 'timeout':
|
||||
named_args[key] = self.option_types[key](options[key])
|
||||
else:
|
||||
named_args[key] = int(options[key])
|
||||
except Exception, e:
|
||||
self.raise_tcl_error("Cannot cast argument '-%s' to type '%s' with exception '%s'."
|
||||
% (key, self.option_types[key], str(e)))
|
||||
@ -207,6 +214,31 @@ class TclCommand(object):
|
||||
|
||||
return named_args, unnamed_args
|
||||
|
||||
|
||||
def raise_tcl_unknown_error(self, unknownException):
|
||||
"""
|
||||
raise Exception if is different type than TclErrorException
|
||||
this is here mainly to show unknown errors inside TCL shell console
|
||||
:param unknownException:
|
||||
:return:
|
||||
"""
|
||||
|
||||
#if not isinstance(unknownException, self.TclErrorException):
|
||||
# self.raise_tcl_error("Unknown error: %s" % str(unknownException))
|
||||
#else:
|
||||
raise unknownException
|
||||
|
||||
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
|
||||
:return: raise exception
|
||||
"""
|
||||
|
||||
# becouse of signaling we cannot call error to TCL from here but when task is finished
|
||||
# also nonsiglaned arwe handled here to better exception handling and diplay after command is finished
|
||||
raise self.app.TclErrorException(text)
|
||||
|
||||
def execute_wrapper(self, *args):
|
||||
"""
|
||||
Command which is called by tcl console when current commands aliases are hit.
|
||||
@ -225,8 +257,10 @@ class TclCommand(object):
|
||||
args, unnamed_args = self.check_args(args)
|
||||
return self.execute(args, unnamed_args)
|
||||
except Exception as unknown:
|
||||
error_info=sys.exc_info()
|
||||
self.log.error("TCL command '%s' failed." % str(self))
|
||||
self.app.raise_tcl_unknown_error(unknown)
|
||||
self.app.display_tcl_error(unknown, error_info)
|
||||
self.raise_tcl_unknown_error(unknown)
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute(self, args, unnamed_args):
|
||||
@ -242,7 +276,6 @@ class TclCommand(object):
|
||||
|
||||
raise NotImplementedError("Please Implement this method")
|
||||
|
||||
|
||||
class TclCommandSignaled(TclCommand):
|
||||
"""
|
||||
!!! I left it here only for demonstration !!!
|
||||
@ -258,15 +291,18 @@ class TclCommandSignaled(TclCommand):
|
||||
it handles all neccessary stuff about blocking and passing exeptions
|
||||
"""
|
||||
|
||||
# default timeout for operation is 10 sec, but it can be much more
|
||||
default_timeout = 10000
|
||||
|
||||
output = None
|
||||
|
||||
def execute_call(self, args, unnamed_args):
|
||||
|
||||
try:
|
||||
self.output = None
|
||||
self.error=None
|
||||
self.error_info=None
|
||||
self.output = self.execute(args, unnamed_args)
|
||||
except Exception as unknown:
|
||||
self.error_info = sys.exc_info()
|
||||
self.error=unknown
|
||||
finally:
|
||||
self.app.shell_command_finished.emit(self)
|
||||
|
||||
@ -281,7 +317,7 @@ class TclCommandSignaled(TclCommand):
|
||||
"""
|
||||
|
||||
@contextmanager
|
||||
def wait_signal(signal, timeout=10000):
|
||||
def wait_signal(signal, timeout=300000):
|
||||
"""Block loop until signal emitted, or timeout (ms) elapses."""
|
||||
loop = QtCore.QEventLoop()
|
||||
|
||||
@ -318,10 +354,10 @@ class TclCommandSignaled(TclCommand):
|
||||
# Restore exception management
|
||||
sys.excepthook = oeh
|
||||
if ex:
|
||||
self.raise_tcl_error(str(ex[0]))
|
||||
raise ex[0]
|
||||
|
||||
if status['timed_out']:
|
||||
self.app.raise_tcl_unknown_error('Operation timed out!')
|
||||
self.app.raise_tcl_unknown_error("Operation timed outed! Consider increasing option '-timeout <miliseconds>' for command or 'set_sys background_timeout <miliseconds>'.")
|
||||
|
||||
try:
|
||||
self.log.debug("TCL command '%s' executed." % str(self.__class__))
|
||||
@ -331,17 +367,15 @@ class TclCommandSignaled(TclCommand):
|
||||
passed_timeout=args['timeout']
|
||||
del args['timeout']
|
||||
else:
|
||||
passed_timeout=self.default_timeout
|
||||
passed_timeout= self.app.defaults['background_timeout']
|
||||
|
||||
# set detail for processing, it will be there until next open or close
|
||||
self.app.shell.open_proccessing(self.get_current_command())
|
||||
|
||||
self.output = None
|
||||
|
||||
def handle_finished(obj):
|
||||
self.app.shell_command_finished.disconnect(handle_finished)
|
||||
# TODO: handle output
|
||||
pass
|
||||
if self.error is not None:
|
||||
self.raise_tcl_unknown_error(self.error)
|
||||
|
||||
self.app.shell_command_finished.connect(handle_finished)
|
||||
|
||||
@ -354,5 +388,7 @@ class TclCommandSignaled(TclCommand):
|
||||
return self.output
|
||||
|
||||
except Exception as unknown:
|
||||
error_info=sys.exc_info()
|
||||
self.log.error("TCL command '%s' failed." % str(self))
|
||||
self.app.raise_tcl_unknown_error(unknown)
|
||||
self.app.display_tcl_error(unknown, error_info)
|
||||
self.raise_tcl_unknown_error(unknown)
|
@ -49,8 +49,7 @@ class TclCommandCncjob(TclCommand.TclCommandSignaled):
|
||||
('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.')
|
||||
('outname', 'Name of the resulting Geometry object.')
|
||||
]),
|
||||
'examples': []
|
||||
}
|
||||
|
81
tclCommands/TclCommandDrillcncjob.py
Normal file
81
tclCommands/TclCommandDrillcncjob.py
Normal file
@ -0,0 +1,81 @@
|
||||
from ObjectCollection import *
|
||||
import TclCommand
|
||||
|
||||
|
||||
class TclCommandDrillcncjob(TclCommand.TclCommandSignaled):
|
||||
"""
|
||||
Tcl shell command to Generates a Drill CNC Job from a Excellon Object.
|
||||
"""
|
||||
|
||||
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
|
||||
aliases = ['drillcncjob']
|
||||
|
||||
# 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([
|
||||
('tools',str),
|
||||
('drillz',float),
|
||||
('travelz',float),
|
||||
('feedrate',float),
|
||||
('spindlespeed',int),
|
||||
('toolchange',bool),
|
||||
('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 Drill CNC Job from a Excellon Object.",
|
||||
'args': collections.OrderedDict([
|
||||
('name', 'Name of the source object.'),
|
||||
('tools', 'Comma separated indexes of tools (example: 1,3 or 2) or select all if not specified.'),
|
||||
('drillz', 'Drill depth into material (example: -2.0).'),
|
||||
('travelz', 'Travel distance above material (example: 2.0).'),
|
||||
('feedrate', 'Drilling feed rate.'),
|
||||
('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'),
|
||||
('toolchange', 'Enable tool changes (example: True).'),
|
||||
('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' not in args:
|
||||
args['outname'] = name + "_cnc"
|
||||
|
||||
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, FlatCAMExcellon):
|
||||
self.raise_tcl_error('Expected FlatCAMExcellon, got %s %s.' % (name, type(obj)))
|
||||
|
||||
def job_init(job_obj, app):
|
||||
job_obj.z_cut = args["drillz"]
|
||||
job_obj.z_move = args["travelz"]
|
||||
job_obj.feedrate = args["feedrate"]
|
||||
job_obj.spindlespeed = args["spindlespeed"] if "spindlespeed" in args else None
|
||||
toolchange = True if "toolchange" in args and args["toolchange"] == 1 else False
|
||||
tools = args["tools"] if "tools" in args else 'all'
|
||||
job_obj.generate_from_excellon_by_tool(obj, tools, toolchange)
|
||||
job_obj.gcode_parse()
|
||||
job_obj.create_geometry()
|
||||
|
||||
self.app.new_object("cncjob", args['outname'], job_init)
|
@ -1,10 +1,11 @@
|
||||
import pkgutil
|
||||
import sys
|
||||
|
||||
# allowed command modules
|
||||
# allowed command modules (please append them alphabetically ordered)
|
||||
import tclCommands.TclCommandAddPolygon
|
||||
import tclCommands.TclCommandAddPolyline
|
||||
import tclCommands.TclCommandCncjob
|
||||
import tclCommands.TclCommandDrillcncjob
|
||||
import tclCommands.TclCommandExportGcode
|
||||
import tclCommands.TclCommandExteriors
|
||||
import tclCommands.TclCommandInteriors
|
||||
@ -19,7 +20,6 @@ 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.
|
||||
|
Loading…
Reference in New Issue
Block a user