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 urllib
|
||||||
import getopt
|
import getopt
|
||||||
import random
|
import random
|
||||||
@ -286,6 +286,8 @@ class App(QtCore.QObject):
|
|||||||
"cncjob_tooldia": 0.016,
|
"cncjob_tooldia": 0.016,
|
||||||
"cncjob_prepend": "",
|
"cncjob_prepend": "",
|
||||||
"cncjob_append": "",
|
"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
|
# Persistence
|
||||||
"last_folder": None,
|
"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
|
this exception is deffined here, to be able catch it if we sucessfully handle all errors from shell command
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def raise_tcl_unknown_error(self, unknownException):
|
def raise_tcl_unknown_error(self, unknownException):
|
||||||
"""
|
"""
|
||||||
raise Exception if is different type than TclErrorException
|
raise Exception if is different type than TclErrorException
|
||||||
|
this is here mainly to show unknown errors inside TCL shell console
|
||||||
:param unknownException:
|
:param unknownException:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@ -694,6 +696,40 @@ class App(QtCore.QObject):
|
|||||||
else:
|
else:
|
||||||
raise unknownException
|
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):
|
def raise_tcl_error(self, text):
|
||||||
"""
|
"""
|
||||||
this method pass exception from python into TCL as error, so we get stacktrace and reason
|
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
|
:return: raise exception
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.tcl.eval('return -code error "%s"' % text)
|
self.display_tcl_error(text)
|
||||||
raise self.TclErrorException(text)
|
raise self.TclErrorException(text)
|
||||||
|
|
||||||
def exec_command(self, text):
|
def exec_command(self, text):
|
||||||
|
@ -2777,7 +2777,7 @@ class CNCjob(Geometry):
|
|||||||
# so we actually are sorting the tools by diameter
|
# so we actually are sorting the tools by diameter
|
||||||
sorted_tools = sorted(exobj.tools.items(), key = lambda x: x[1])
|
sorted_tools = sorted(exobj.tools.items(), key = lambda x: x[1])
|
||||||
if tools == "all":
|
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))
|
log.debug("Tools 'all' and sorted are: %s" % str(tools))
|
||||||
else:
|
else:
|
||||||
selected_tools = [x.strip() for x in tools.split(",")] # we strip spaces and also separate the tools by ','
|
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:
|
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)
|
# Tool change sequence (optional)
|
||||||
if toolchange:
|
if toolchange:
|
||||||
gcode += "G00 Z%.4f\n" % toolchangez
|
gcode += "G00 Z%.4f\n" % toolchangez
|
||||||
|
@ -125,6 +125,10 @@ class TclCommand(object):
|
|||||||
for key, value in self.help['args'].items():
|
for key, value in self.help['args'].items():
|
||||||
help_string.append(get_decorated_argument(key, value))
|
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']:
|
for example in self.help['examples']:
|
||||||
help_string.append(get_decorated_example(example))
|
help_string.append(get_decorated_example(example))
|
||||||
|
|
||||||
@ -192,10 +196,13 @@ class TclCommand(object):
|
|||||||
|
|
||||||
# check options
|
# check options
|
||||||
for key in 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)
|
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||||
try:
|
try:
|
||||||
|
if key != 'timeout':
|
||||||
named_args[key] = self.option_types[key](options[key])
|
named_args[key] = self.option_types[key](options[key])
|
||||||
|
else:
|
||||||
|
named_args[key] = int(options[key])
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.raise_tcl_error("Cannot cast argument '-%s' to type '%s' with exception '%s'."
|
self.raise_tcl_error("Cannot cast argument '-%s' to type '%s' with exception '%s'."
|
||||||
% (key, self.option_types[key], str(e)))
|
% (key, self.option_types[key], str(e)))
|
||||||
@ -207,6 +214,31 @@ class TclCommand(object):
|
|||||||
|
|
||||||
return named_args, unnamed_args
|
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):
|
def execute_wrapper(self, *args):
|
||||||
"""
|
"""
|
||||||
Command which is called by tcl console when current commands aliases are hit.
|
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)
|
args, unnamed_args = self.check_args(args)
|
||||||
return self.execute(args, unnamed_args)
|
return self.execute(args, unnamed_args)
|
||||||
except Exception as unknown:
|
except Exception as unknown:
|
||||||
|
error_info=sys.exc_info()
|
||||||
self.log.error("TCL command '%s' failed." % str(self))
|
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
|
@abc.abstractmethod
|
||||||
def execute(self, args, unnamed_args):
|
def execute(self, args, unnamed_args):
|
||||||
@ -242,7 +276,6 @@ class TclCommand(object):
|
|||||||
|
|
||||||
raise NotImplementedError("Please Implement this method")
|
raise NotImplementedError("Please Implement this method")
|
||||||
|
|
||||||
|
|
||||||
class TclCommandSignaled(TclCommand):
|
class TclCommandSignaled(TclCommand):
|
||||||
"""
|
"""
|
||||||
!!! I left it here only for demonstration !!!
|
!!! I left it here only for demonstration !!!
|
||||||
@ -258,15 +291,18 @@ class TclCommandSignaled(TclCommand):
|
|||||||
it handles all neccessary stuff about blocking and passing exeptions
|
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
|
output = None
|
||||||
|
|
||||||
def execute_call(self, args, unnamed_args):
|
def execute_call(self, args, unnamed_args):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
self.output = None
|
||||||
|
self.error=None
|
||||||
|
self.error_info=None
|
||||||
self.output = self.execute(args, unnamed_args)
|
self.output = self.execute(args, unnamed_args)
|
||||||
|
except Exception as unknown:
|
||||||
|
self.error_info = sys.exc_info()
|
||||||
|
self.error=unknown
|
||||||
finally:
|
finally:
|
||||||
self.app.shell_command_finished.emit(self)
|
self.app.shell_command_finished.emit(self)
|
||||||
|
|
||||||
@ -281,7 +317,7 @@ class TclCommandSignaled(TclCommand):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def wait_signal(signal, timeout=10000):
|
def wait_signal(signal, timeout=300000):
|
||||||
"""Block loop until signal emitted, or timeout (ms) elapses."""
|
"""Block loop until signal emitted, or timeout (ms) elapses."""
|
||||||
loop = QtCore.QEventLoop()
|
loop = QtCore.QEventLoop()
|
||||||
|
|
||||||
@ -318,10 +354,10 @@ class TclCommandSignaled(TclCommand):
|
|||||||
# Restore exception management
|
# Restore exception management
|
||||||
sys.excepthook = oeh
|
sys.excepthook = oeh
|
||||||
if ex:
|
if ex:
|
||||||
self.raise_tcl_error(str(ex[0]))
|
raise ex[0]
|
||||||
|
|
||||||
if status['timed_out']:
|
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:
|
try:
|
||||||
self.log.debug("TCL command '%s' executed." % str(self.__class__))
|
self.log.debug("TCL command '%s' executed." % str(self.__class__))
|
||||||
@ -331,17 +367,15 @@ class TclCommandSignaled(TclCommand):
|
|||||||
passed_timeout=args['timeout']
|
passed_timeout=args['timeout']
|
||||||
del args['timeout']
|
del args['timeout']
|
||||||
else:
|
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
|
# set detail for processing, it will be there until next open or close
|
||||||
self.app.shell.open_proccessing(self.get_current_command())
|
self.app.shell.open_proccessing(self.get_current_command())
|
||||||
|
|
||||||
self.output = None
|
|
||||||
|
|
||||||
def handle_finished(obj):
|
def handle_finished(obj):
|
||||||
self.app.shell_command_finished.disconnect(handle_finished)
|
self.app.shell_command_finished.disconnect(handle_finished)
|
||||||
# TODO: handle output
|
if self.error is not None:
|
||||||
pass
|
self.raise_tcl_unknown_error(self.error)
|
||||||
|
|
||||||
self.app.shell_command_finished.connect(handle_finished)
|
self.app.shell_command_finished.connect(handle_finished)
|
||||||
|
|
||||||
@ -354,5 +388,7 @@ class TclCommandSignaled(TclCommand):
|
|||||||
return self.output
|
return self.output
|
||||||
|
|
||||||
except Exception as unknown:
|
except Exception as unknown:
|
||||||
|
error_info=sys.exc_info()
|
||||||
self.log.error("TCL command '%s' failed." % str(self))
|
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).'),
|
('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'),
|
||||||
('multidepth', 'Use or not multidepth cnccut.'),
|
('multidepth', 'Use or not multidepth cnccut.'),
|
||||||
('depthperpass', 'Height of one layer for multidepth.'),
|
('depthperpass', 'Height of one layer for multidepth.'),
|
||||||
('outname', 'Name of the resulting Geometry object.'),
|
('outname', 'Name of the resulting Geometry object.')
|
||||||
('timeout', 'Max wait for job timeout before error.')
|
|
||||||
]),
|
]),
|
||||||
'examples': []
|
'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 pkgutil
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# allowed command modules
|
# allowed command modules (please append them alphabetically ordered)
|
||||||
import tclCommands.TclCommandAddPolygon
|
import tclCommands.TclCommandAddPolygon
|
||||||
import tclCommands.TclCommandAddPolyline
|
import tclCommands.TclCommandAddPolyline
|
||||||
import tclCommands.TclCommandCncjob
|
import tclCommands.TclCommandCncjob
|
||||||
|
import tclCommands.TclCommandDrillcncjob
|
||||||
import tclCommands.TclCommandExportGcode
|
import tclCommands.TclCommandExportGcode
|
||||||
import tclCommands.TclCommandExteriors
|
import tclCommands.TclCommandExteriors
|
||||||
import tclCommands.TclCommandInteriors
|
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)
|
module = loader.find_module(name).load_module(name)
|
||||||
__all__.append(name)
|
__all__.append(name)
|
||||||
|
|
||||||
|
|
||||||
def register_all_commands(app, commands):
|
def register_all_commands(app, commands):
|
||||||
"""
|
"""
|
||||||
Static method which register all known commands.
|
Static method which register all known commands.
|
||||||
|
Loading…
Reference in New Issue
Block a user