- refactored some of the code in the App class and created a new Tcl Command named Help

This commit is contained in:
Marius Stanciu 2020-04-22 23:00:54 +03:00 committed by Marius
parent 66d9ddd402
commit a1499158c2
10 changed files with 551 additions and 459 deletions

View File

@ -12,6 +12,7 @@ CHANGELOG for FlatCAM beta
- added a new feature, project auto-saving controlled from Edit -> Preferences -> General -> APP. Preferences -> Enable Auto Save checkbox
- fixed some bugs in the Tcl Commands
- modified the Tcl Commands to be able to use as boolean values keywords with lower case like 'false' instead of expected 'False'
- refactored some of the code in the App class and created a new Tcl Command named Help
20.04.2020

View File

@ -2556,6 +2556,9 @@ class App(QtCore.QObject):
# this will hold the TCL instance
self.tcl = None
# the actual variable will be redeclared in setup_tcl()
self.tcl_commands_storage = None
self.init_tcl()
self.shell = FCShell(self, version=self.version)
@ -2566,14 +2569,7 @@ class App(QtCore.QObject):
self.shell.append_output("FlatCAM %s - " % self.version)
self.shell.append_output(_("Type >help< to get started\n\n"))
self.ui.shell_dock = QtWidgets.QDockWidget("FlatCAM TCL Shell")
self.ui.shell_dock.setObjectName('Shell_DockWidget')
self.ui.shell_dock.setWidget(self.shell)
self.ui.shell_dock.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
self.ui.shell_dock.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable |
QtWidgets.QDockWidget.DockWidgetClosable)
self.ui.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.ui.shell_dock)
# show TCL shell at start-up based on the Menu -? Edit -> Preferences setting.
if self.defaults["global_shell_at_startup"]:
@ -2883,7 +2879,7 @@ class App(QtCore.QObject):
# if there are Windows paths then replace the path separator with a Unix like one
if sys.platform == 'win32':
command_tcl_formatted = command_tcl_formatted.replace('\\', '/')
self.shell._sysShell.exec_command(command_tcl_formatted, no_echo=True)
self.shell.exec_command(command_tcl_formatted, no_echo=True)
except Exception as ext:
print("ERROR: ", ext)
sys.exit(2)
@ -2903,9 +2899,9 @@ class App(QtCore.QObject):
# color=QtGui.QColor("gray"))
cmd_line_shellfile_text = myfile.read()
if self.cmd_line_headless != 1:
self.shell._sysShell.exec_command(cmd_line_shellfile_text)
self.shell.exec_command(cmd_line_shellfile_text)
else:
self.shell._sysShell.exec_command(cmd_line_shellfile_text, no_echo=True)
self.shell.exec_command(cmd_line_shellfile_text, no_echo=True)
except Exception as ext:
print("ERROR: ", ext)
@ -3703,209 +3699,6 @@ class App(QtCore.QObject):
else:
self.defaults['global_stats'][resource] = 1
def init_tcl(self):
"""
Initialize the TCL Shell. A dock widget that holds the GUI interface to the FlatCAM command line.
:return: None
"""
if hasattr(self, 'tcl') and self.tcl is not None:
# self.tcl = None
# TODO we need to clean non default variables and procedures here
# new object cannot be used here as it will not remember values created for next passes,
# because tcl was execudted in old instance of TCL
pass
else:
self.tcl = tk.Tcl()
self.setup_shell()
self.log.debug("TCL Shell has been initialized.")
# TODO: This shouldn't be here.
class TclErrorException(Exception):
"""
this exception is defined here, to be able catch it if we successfully handle all errors from shell command
"""
pass
def shell_message(self, msg, show=False, error=False, warning=False, success=False, selected=False):
"""
Shows a message on the FlatCAM Shell
:param msg: Message to display.
:param show: Opens the shell.
:param error: Shows the message as an error.
:param warning: Shows the message as an warning.
:param success: Shows the message as an success.
:param selected: Indicate that something was selected on canvas
:return: None
"""
if show:
self.ui.shell_dock.show()
try:
if error:
self.shell.append_error(msg + "\n")
elif warning:
self.shell.append_warning(msg + "\n")
elif success:
self.shell.append_success(msg + "\n")
elif selected:
self.shell.append_selected(msg + "\n")
else:
self.shell.append_output(msg + "\n")
except AttributeError:
log.debug("shell_message() is called before Shell Class is instantiated. The message is: %s", str(msg))
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 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
:param error_info: Some informations about the error
: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['global_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
:param text: text of error
:return: raise exception
"""
self.display_tcl_error(text)
raise self.TclErrorException(text)
def exec_command(self, text, no_echo=False):
"""
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
Also handles execution in separated threads
:param text: FlatCAM TclCommand with parameters
:param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
will create crashes of the _Expandable_Edit widget
:return: output if there was any
"""
self.report_usage('exec_command')
result = self.exec_command_test(text, False, no_echo=no_echo)
# MS: added this method call so the geometry is updated once the TCL command is executed
# if no_plot is None:
# self.plot_all()
return result
def exec_command_test(self, text, reraise=True, no_echo=False):
"""
Same as exec_command(...) with additional control over exceptions.
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
:param text: Input command
:param reraise: Re-raise TclError exceptions in Python (mostly for unittests).
:param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
will create crashes of the _Expandable_Edit widget
:return: Output from the command
"""
tcl_command_string = str(text)
try:
if no_echo is False:
self.shell.open_processing() # Disables input box.
result = self.tcl.eval(str(tcl_command_string))
if result != 'None' and no_echo is False:
self.shell.append_output(result + '\n')
except tk.TclError as e:
# This will display more precise answer if something in TCL shell fails
result = self.tcl.eval("set errorInfo")
self.log.error("Exec command Exception: %s" % (result + '\n'))
if no_echo is False:
self.shell.append_error('ERROR: ' + result + '\n')
# Show error in console and just return or in test raise exception
if reraise:
raise e
finally:
if no_echo is False:
self.shell.close_processing()
pass
return result
# """
# Code below is unsused. Saved for later.
# """
# parts = re.findall(r'([\w\\:\.]+|".*?")+', text)
# parts = [p.replace('\n', '').replace('"', '') for p in parts]
# self.log.debug(parts)
# try:
# if parts[0] not in commands:
# self.shell.append_error("Unknown command\n")
# return
#
# #import inspect
# #inspect.getargspec(someMethod)
# if (type(commands[parts[0]]["params"]) is not list and len(parts)-1 != commands[parts[0]]["params"]) or \
# (type(commands[parts[0]]["params"]) is list and len(parts)-1 not in commands[parts[0]]["params"]):
# self.shell.append_error(
# "Command %s takes %d arguments. %d given.\n" %
# (parts[0], commands[parts[0]]["params"], len(parts)-1)
# )
# return
#
# cmdfcn = commands[parts[0]]["fcn"]
# cmdconv = commands[parts[0]]["converters"]
# if len(parts) - 1 > 0:
# retval = cmdfcn(*[cmdconv[i](parts[i + 1]) for i in range(len(parts)-1)])
# else:
# retval = cmdfcn()
# retfcn = commands[parts[0]]["retfcn"]
# if retval and retfcn(retval):
# self.shell.append_output(retfcn(retval) + "\n")
#
# except Exception as e:
# #self.shell.append_error(''.join(traceback.format_exc()))
# #self.shell.append_error("?\n")
# self.shell.append_error(str(e) + "\n")
def info(self, msg):
"""
Informs the user. Normally on the status bar, optionally
@ -10464,9 +10257,9 @@ class App(QtCore.QObject):
with open(filename, "r") as tcl_script:
cmd_line_shellfile_content = tcl_script.read()
if self.cmd_line_headless != 1:
self.shell._sysShell.exec_command(cmd_line_shellfile_content)
self.shell.exec_command(cmd_line_shellfile_content)
else:
self.shell._sysShell.exec_command(cmd_line_shellfile_content, no_echo=True)
self.shell.exec_command(cmd_line_shellfile_content, no_echo=True)
if silent is False:
self.inform.emit('[success] %s' % _("TCL script file opened in Code Editor and executed."))
@ -11865,231 +11658,6 @@ class App(QtCore.QObject):
"""
self.ui.progress_bar.setValue(int(percentage))
def setup_shell(self):
"""
Creates shell functions. Runs once at startup.
:return: None
"""
self.log.debug("setup_shell()")
def shelp(p=None):
if not p:
cmd_enum = _("Available commands:\n")
displayed_text = []
try:
# find the maximum length of a command name
max_len = 0
for cmd_name in commands:
curr_len = len(cmd_name)
if curr_len > max_len:
max_len = curr_len
max_tabs = math.ceil(max_len / 8)
for cmd_name in sorted(commands):
cmd_description = commands[cmd_name]['description']
curr_len = len(cmd_name)
tabs = '\t'
# make sure to add the right number of tabs (1 tab = 8 spaces) so all the commands
# descriptions are aligned
if curr_len == max_len:
cmd_line_txt = ' %s%s%s' % (str(cmd_name), tabs, cmd_description)
else:
nr_tabs = 0
for x in range(max_tabs):
if curr_len <= (x*8):
nr_tabs += 1
# nr_tabs = 2 if curr_len <= 8 else 1
cmd_line_txt = ' %s%s%s' % (str(cmd_name), nr_tabs*tabs, cmd_description)
displayed_text.append(cmd_line_txt)
except Exception as err:
log.debug("App.setup_shell.shelp() when run as 'help' --> %s" % str(err))
displayed_text = [' %s' % cmd for cmd in sorted(commands)]
cmd_enum += '\n'.join(displayed_text)
cmd_enum += '\n\n%s\n%s' % (_("Type help <command_name> for usage."), _("Example: help open_gerber"))
return cmd_enum
if p not in commands:
return "Unknown command: %s" % p
return commands[p]["help"]
# --- Migrated to new architecture ---
# def options(name):
# ops = self.collection.get_by_name(str(name)).options
# return '\n'.join(["%s: %s" % (o, ops[o]) for o in ops])
def h(*args):
"""
Pre-processes arguments to detect '-keyword value' pairs into dictionary
and standalone parameters into list.
"""
kwa = {}
a = []
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:
a.append(args[i])
else:
kwa[name] = args[i]
name = None
return a, kwa
@contextmanager
def wait_signal(signal, timeout=10000):
"""
Block loop until signal emitted, timeout (ms) elapses
or unhandled exception happens in a thread.
:param timeout: time after which the loop is exited
:param signal: Signal to wait for.
"""
loop = QtCore.QEventLoop()
# Normal termination
signal.connect(loop.quit)
# Termination by exception in thread
self.thread_exception.connect(loop.quit)
status = {'timed_out': False}
def report_quit():
status['timed_out'] = True
loop.quit()
yield
# Temporarily change how exceptions are managed.
oeh = sys.excepthook
ex = []
def except_hook(type_, value, traceback_):
ex.append(value)
oeh(type_, value, traceback_)
sys.excepthook = except_hook
# Terminate on timeout
if timeout is not None:
QtCore.QTimer.singleShot(timeout, report_quit)
# # ## Block ## ##
loop.exec_()
# Restore exception management
sys.excepthook = oeh
if ex:
self.raiseTclError(str(ex[0]))
if status['timed_out']:
raise Exception('Timed out!')
def make_docs():
output = ''
import collections
od = collections.OrderedDict(sorted(commands.items()))
for cmd_, val in od.items():
output += cmd_ + ' \n' + ''.join(['~'] * len(cmd_)) + '\n'
t = val['help']
usage_i = t.find('>')
if usage_i < 0:
expl = t
output += expl + '\n\n'
continue
expl = t[:usage_i - 1]
output += expl + '\n\n'
end_usage_i = t[usage_i:].find('\n')
if end_usage_i < 0:
end_usage_i = len(t[usage_i:])
output += ' ' + t[usage_i:] + '\n No parameters.\n'
else:
extras = t[usage_i+end_usage_i+1:]
parts = [s.strip() for s in extras.split('\n')]
output += ' ' + t[usage_i:usage_i+end_usage_i] + '\n'
for p in parts:
output += ' ' + p + '\n\n'
return output
'''
Howto implement TCL shell commands:
All parameters passed to command should be possible to set as None and test it afterwards.
This is because we need to see error caused in tcl,
if None value as default parameter is not allowed TCL will return empty error.
Use:
def mycommand(name=None,...):
Test it like this:
if name is None:
self.raise_tcl_error('Argument name is missing.')
When error ocurre, always use raise_tcl_error, never return "sometext" on error,
otherwise we will miss it and processing will silently continue.
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.
This behavior works only within main thread,
errors with promissed tasks can be catched and detected only with log.
TODO: this problem have to be addressed somehow, maybe rewrite promissing to be blocking somehow for
TCL shell.
Kamil's comment: I will rewrite existing TCL commands from time to time to follow this rules.
'''
commands = {
'help': {
'fcn': shelp,
'help': _("Shows list of commands."),
'description': ''
},
}
# Import/overwrite tcl commands as objects of TclCommand descendants
# This modifies the variable 'commands'.
tclCommands.register_all_commands(self, commands)
# Add commands to the tcl interpreter
for cmd in commands:
self.tcl.createcommand(cmd, commands[cmd]['fcn'])
# Make the tcl puts function return instead of print to stdout
self.tcl.eval('''
rename puts original_puts
proc puts {args} {
if {[llength $args] == 1} {
return "[lindex $args 0]"
} else {
eval original_puts $args
}
}
''')
def setup_recent_items(self):
"""
Setup a dictionary with the recent files accessed, organized by type
@ -13040,6 +12608,295 @@ class App(QtCore.QObject):
self.options.update(self.defaults)
# self.options_write_form()
def init_tcl(self):
"""
Initialize the TCL Shell. A dock widget that holds the GUI interface to the FlatCAM command line.
:return: None
"""
if hasattr(self, 'tcl') and self.tcl is not None:
# self.tcl = None
# TODO we need to clean non default variables and procedures here
# new object cannot be used here as it will not remember values created for next passes,
# because tcl was execudted in old instance of TCL
pass
else:
self.tcl = tk.Tcl()
self.setup_shell()
self.log.debug("TCL Shell has been initialized.")
def setup_shell(self):
"""
Creates shell functions. Runs once at startup.
:return: None
"""
self.log.debug("setup_shell()")
def shelp(p=None):
pass
# --- Migrated to new architecture ---
# def options(name):
# ops = self.collection.get_by_name(str(name)).options
# return '\n'.join(["%s: %s" % (o, ops[o]) for o in ops])
def h(*args):
"""
Pre-processes arguments to detect '-keyword value' pairs into dictionary
and standalone parameters into list.
"""
kwa = {}
a = []
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:
a.append(args[i])
else:
kwa[name] = args[i]
name = None
return a, kwa
@contextmanager
def wait_signal(signal, timeout=10000):
"""
Block loop until signal emitted, timeout (ms) elapses
or unhandled exception happens in a thread.
:param timeout: time after which the loop is exited
:param signal: Signal to wait for.
"""
loop = QtCore.QEventLoop()
# Normal termination
signal.connect(loop.quit)
# Termination by exception in thread
self.thread_exception.connect(loop.quit)
status = {'timed_out': False}
def report_quit():
status['timed_out'] = True
loop.quit()
yield
# Temporarily change how exceptions are managed.
oeh = sys.excepthook
ex = []
def except_hook(type_, value, traceback_):
ex.append(value)
oeh(type_, value, traceback_)
sys.excepthook = except_hook
# Terminate on timeout
if timeout is not None:
QtCore.QTimer.singleShot(timeout, report_quit)
# # ## Block ## ##
loop.exec_()
# Restore exception management
sys.excepthook = oeh
if ex:
self.raise_tcl_error(str(ex[0]))
if status['timed_out']:
raise Exception('Timed out!')
def make_docs():
output = ''
import collections
od = collections.OrderedDict(sorted(self.tcl_commands_storage.items()))
for cmd_, val in od.items():
output += cmd_ + ' \n' + ''.join(['~'] * len(cmd_)) + '\n'
t = val['help']
usage_i = t.find('>')
if usage_i < 0:
expl = t
output += expl + '\n\n'
continue
expl = t[:usage_i - 1]
output += expl + '\n\n'
end_usage_i = t[usage_i:].find('\n')
if end_usage_i < 0:
end_usage_i = len(t[usage_i:])
output += ' ' + t[usage_i:] + '\n No parameters.\n'
else:
extras = t[usage_i + end_usage_i + 1:]
parts = [s.strip() for s in extras.split('\n')]
output += ' ' + t[usage_i:usage_i + end_usage_i] + '\n'
for p in parts:
output += ' ' + p + '\n\n'
return output
'''
Howto implement TCL shell commands:
All parameters passed to command should be possible to set as None and test it afterwards.
This is because we need to see error caused in tcl,
if None value as default parameter is not allowed TCL will return empty error.
Use:
def mycommand(name=None,...):
Test it like this:
if name is None:
self.raise_tcl_error('Argument name is missing.')
When error ocurre, always use raise_tcl_error, never return "sometext" on error,
otherwise we will miss it and processing will silently continue.
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.
This behavior works only within main thread,
errors with promissed tasks can be catched and detected only with log.
TODO: this problem have to be addressed somehow, maybe rewrite promissing to be blocking somehow for
TCL shell.
Kamil's comment: I will rewrite existing TCL commands from time to time to follow this rules.
'''
self.tcl_commands_storage = {}
# commands = {
# 'help': {
# 'fcn': shelp,
# 'help': _("Shows list of commands."),
# 'description': ''
# },
# }
# Import/overwrite tcl commands as objects of TclCommand descendants
# This modifies the variable 'commands'.
tclCommands.register_all_commands(self, self.tcl_commands_storage)
# Add commands to the tcl interpreter
for cmd in self.tcl_commands_storage:
self.tcl.createcommand(cmd, self.tcl_commands_storage[cmd]['fcn'])
# Make the tcl puts function return instead of print to stdout
self.tcl.eval('''
rename puts original_puts
proc puts {args} {
if {[llength $args] == 1} {
return "[lindex $args 0]"
} else {
eval original_puts $args
}
}
''')
# TODO: This shouldn't be here.
class TclErrorException(Exception):
"""
this exception is defined here, to be able catch it if we successfully handle all errors from shell command
"""
pass
def shell_message(self, msg, show=False, error=False, warning=False, success=False, selected=False):
"""
Shows a message on the FlatCAM Shell
:param msg: Message to display.
:param show: Opens the shell.
:param error: Shows the message as an error.
:param warning: Shows the message as an warning.
:param success: Shows the message as an success.
:param selected: Indicate that something was selected on canvas
:return: None
"""
if show:
self.ui.shell_dock.show()
try:
if error:
self.shell.append_error(msg + "\n")
elif warning:
self.shell.append_warning(msg + "\n")
elif success:
self.shell.append_success(msg + "\n")
elif selected:
self.shell.append_selected(msg + "\n")
else:
self.shell.append_output(msg + "\n")
except AttributeError:
log.debug("shell_message() is called before Shell Class is instantiated. The message is: %s", str(msg))
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 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
:param error_info: Some informations about the error
: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['global_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
:param text: text of error
:return: raise exception
"""
self.display_tcl_error(text)
raise self.TclErrorException(text)
class ArgsThread(QtCore.QObject):
open_signal = pyqtSignal(list)

View File

@ -252,3 +252,11 @@ class FlatCAMTool(QtWidgets.QWidget):
(_("Edited value is out of range"), minval, maxval))
else:
self.app.inform.emit('[success] %s' % _("Edited value is within limits."))
def sizeHint(self):
"""
I've overloaded this just in case I will need to make changes in the future to enforce dimensions
:return:
"""
default_hint_size = super(FlatCAMTool, self).sizeHint()
return QtCore.QSize(default_hint_size.width(), default_hint_size.height())

View File

@ -42,13 +42,24 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# Divine icon pack by Ipapun @ finicons.com
# ################################## ##
# ## BUILDING THE GUI IS DONE HERE # ##
# ################################## ##
# #######################################################################
# ############ BUILDING THE GUI IS EXECUTED HERE ########################
# #######################################################################
# ######### ##
# ## Menu # ##
# ######### ##
# #######################################################################
# ####################### TCL Shell DOCK ################################
# #######################################################################
self.shell_dock = QtWidgets.QDockWidget("FlatCAM TCL Shell")
self.shell_dock.setObjectName('Shell_DockWidget')
self.shell_dock.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
self.shell_dock.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable |
QtWidgets.QDockWidget.DockWidgetClosable)
self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.shell_dock)
# #######################################################################
# ###################### Menu BUILDING ##################################
# #######################################################################
self.menu = self.menuBar()
self.menu_toggle_nb = QtWidgets.QAction(
@ -735,7 +746,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# ########################################################################
# IMPORTANT #
# The order: SPITTER -> NOTEBOOK -> SNAP TOOLBAR is important and without it the GUI will not be initialized as
# The order: SPLITTER -> NOTEBOOK -> SNAP TOOLBAR is important and without it the GUI will not be initialized as
# desired.
self.splitter = QtWidgets.QSplitter()
self.setCentralWidget(self.splitter)

View File

@ -159,7 +159,7 @@ class DblSidedTool(FlatCAMTool):
self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Mirror Parameters"))
self.param_label.setToolTip('%s.' % _("Parameters for the mirror operation"))
grid_lay1.addWidget(self.param_label, 0, 0, 1, 3)
grid_lay1.addWidget(self.param_label, 0, 0, 1, 2)
# ## Axis
self.mirax_label = QtWidgets.QLabel('%s:' % _("Mirror Axis"))

View File

@ -14,6 +14,8 @@ from flatcamGUI.GUIElements import _BrowserTextEdit, _ExpandableTextEdit
import html
import sys
import tkinter as tk
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
@ -227,6 +229,12 @@ class TermWidget(QWidget):
class FCShell(TermWidget):
def __init__(self, sysShell, version, *args):
"""
:param sysShell: When instantiated the sysShell will be actually the FlatCAMApp.App() class
:param version: FlatCAM version string
:param args: Parameters passed to the TermWidget parent class
"""
TermWidget.__init__(self, version, *args)
self._sysShell = sysShell
@ -246,4 +254,94 @@ class FCShell(TermWidget):
return True
def child_exec_command(self, text):
self._sysShell.exec_command(text)
self.exec_command(text)
def exec_command(self, text, no_echo=False):
"""
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
Also handles execution in separated threads
:param text: FlatCAM TclCommand with parameters
:param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
will create crashes of the _Expandable_Edit widget
:return: output if there was any
"""
self._sysShell.report_usage('exec_command')
return self.exec_command_test(text, False, no_echo=no_echo)
def exec_command_test(self, text, reraise=True, no_echo=False):
"""
Same as exec_command(...) with additional control over exceptions.
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
:param text: Input command
:param reraise: Re-raise TclError exceptions in Python (mostly for unittests).
:param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
will create crashes of the _Expandable_Edit widget
:return: Output from the command
"""
tcl_command_string = str(text)
try:
if no_echo is False:
self.open_processing() # Disables input box.
result = self._sysShell.tcl.eval(str(tcl_command_string))
if result != 'None' and no_echo is False:
self.append_output(result + '\n')
except tk.TclError as e:
# This will display more precise answer if something in TCL shell fails
result = self._sysShell.tcl.eval("set errorInfo")
self._sysShell.log.error("Exec command Exception: %s" % (result + '\n'))
if no_echo is False:
self.append_error('ERROR: ' + result + '\n')
# Show error in console and just return or in test raise exception
if reraise:
raise e
finally:
if no_echo is False:
self.close_processing()
pass
return result
# """
# Code below is unsused. Saved for later.
# """
# parts = re.findall(r'([\w\\:\.]+|".*?")+', text)
# parts = [p.replace('\n', '').replace('"', '') for p in parts]
# self.log.debug(parts)
# try:
# if parts[0] not in commands:
# self.shell.append_error("Unknown command\n")
# return
#
# #import inspect
# #inspect.getargspec(someMethod)
# if (type(commands[parts[0]]["params"]) is not list and len(parts)-1 != commands[parts[0]]["params"]) or \
# (type(commands[parts[0]]["params"]) is list and len(parts)-1 not in commands[parts[0]]["params"]):
# self.shell.append_error(
# "Command %s takes %d arguments. %d given.\n" %
# (parts[0], commands[parts[0]]["params"], len(parts)-1)
# )
# return
#
# cmdfcn = commands[parts[0]]["fcn"]
# cmdconv = commands[parts[0]]["converters"]
# if len(parts) - 1 > 0:
# retval = cmdfcn(*[cmdconv[i](parts[i + 1]) for i in range(len(parts)-1)])
# else:
# retval = cmdfcn()
# retfcn = commands[parts[0]]["retfcn"]
# if retval and retfcn(retval):
# self.shell.append_output(retfcn(retval) + "\n")
#
# except Exception as e:
# #self.shell.append_error(''.join(traceback.format_exc()))
# #self.shell.append_error("?\n")
# self.shell.append_error(str(e) + "\n")

View File

@ -33,8 +33,6 @@ class ToolTransform(FlatCAMTool):
FlatCAMTool.__init__(self, app)
self.decimals = self.app.decimals
self.transform_lay = QtWidgets.QVBoxLayout()
self.layout.addLayout(self.transform_lay)
# ## Title
title_label = QtWidgets.QLabel("%s" % self.toolName)
title_label.setStyleSheet("""
@ -44,12 +42,12 @@ class ToolTransform(FlatCAMTool):
font-weight: bold;
}
""")
self.transform_lay.addWidget(title_label)
self.transform_lay.addWidget(QtWidgets.QLabel(''))
self.layout.addWidget(title_label)
self.layout.addWidget(QtWidgets.QLabel(''))
# ## Layout
grid0 = QtWidgets.QGridLayout()
self.transform_lay.addLayout(grid0)
self.layout.addLayout(grid0)
grid0.setColumnStretch(0, 0)
grid0.setColumnStretch(1, 1)
grid0.setColumnStretch(2, 0)
@ -206,7 +204,7 @@ class ToolTransform(FlatCAMTool):
self.ois_scale = OptionalInputSection(self.scale_link_cb, [self.scaley_entry, self.scaley_button], logic=False)
grid0.addWidget(self.scale_link_cb, 10, 0)
grid0.addWidget(self.scale_zero_ref_cb, 10, 1)
grid0.addWidget(self.scale_zero_ref_cb, 10, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@ -395,7 +393,7 @@ class ToolTransform(FlatCAMTool):
grid0.addWidget(QtWidgets.QLabel(''), 26, 0, 1, 3)
self.transform_lay.addStretch()
self.layout.addStretch()
# ## Reset Tool
self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
@ -408,7 +406,7 @@ class ToolTransform(FlatCAMTool):
font-weight: bold;
}
""")
self.transform_lay.addWidget(self.reset_button)
self.layout.addWidget(self.reset_button)
# ## Signals
self.rotate_button.clicked.connect(self.on_rotate)

View File

@ -3,8 +3,7 @@ import re
import FlatCAMApp
import abc
import collections
from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt
from PyQt5 import QtCore
from contextlib import contextmanager

View File

@ -0,0 +1,119 @@
# ##########################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# File Author: Marius Adrian Stanciu (c) #
# Content was borrowed from FlatCAM proper #
# Date: 4/22/2020 #
# MIT Licence #
# ##########################################################
from tclCommands.TclCommand import TclCommand
import collections
import math
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class TclCommandHelp(TclCommand):
"""
Tcl shell command to get the value of a system variable
example:
get_sys excellon_zeros
"""
# List of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
aliases = ['help']
description = '%s %s' % ("--", "PRINTS to TCL the HELP.")
# 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([
])
# array of mandatory options for current Tcl command: required = {'name','outname'}
required = []
# structured help for current command, args needs to be ordered
help = {
'main': "Returns to TCL the value for the entered system variable.",
'args': collections.OrderedDict([
('name', 'Name of a Tcl Command for which to display the Help.'),
]),
'examples': ['help add_circle']
}
def execute(self, args, unnamed_args):
"""
:param args:
:param unnamed_args:
:return:
"""
print(self.app.tcl_commands_storage)
if 'name' in args:
name = args['name']
if name not in self.app.tcl_commands_storage:
return "Unknown command: %s" % name
print(self.app.tcl_commands_storage[name]["help"])
else:
if args is None:
cmd_enum = _("Available commands:\n")
displayed_text = []
try:
# find the maximum length of a command name
max_len = 0
for cmd_name in self.app.tcl_commands_storage:
curr_len = len(cmd_name)
if curr_len > max_len:
max_len = curr_len
max_tabs = math.ceil(max_len / 8)
for cmd_name in sorted(self.app.tcl_commands_storage):
cmd_description = self.app.tcl_commands_storage[cmd_name]['description']
curr_len = len(cmd_name)
tabs = '\t'
cmd_name_colored = "<span style=\" color:#ff0000;\" >"
cmd_name_colored += str(cmd_name)
cmd_name_colored += "</span>"
# make sure to add the right number of tabs (1 tab = 8 spaces) so all the commands
# descriptions are aligned
if curr_len == max_len:
cmd_line_txt = ' %s%s%s' % (cmd_name_colored, tabs, cmd_description)
else:
nr_tabs = 0
for x in range(max_tabs):
if curr_len <= (x * 8):
nr_tabs += 1
# nr_tabs = 2 if curr_len <= 8 else 1
cmd_line_txt = ' %s%s%s' % (cmd_name_colored, nr_tabs * tabs, cmd_description)
displayed_text.append(cmd_line_txt)
except Exception as err:
self.app.log.debug("App.setup_shell.shelp() when run as 'help' --> %s" % str(err))
displayed_text = [' %s' % cmd for cmd in sorted(self.app.tcl_commands_storage)]
cmd_enum += '\n'.join(displayed_text)
cmd_enum += '\n\n%s\n%s' % (_("Type help <command_name> for usage."), _("Example: help open_gerber"))
print(cmd_enum)

View File

@ -93,6 +93,7 @@ def register_all_commands(app, commands):
tcl_modules = {k: v for k, v in list(sys.modules.items()) if k.startswith('tclCommands.TclCommand')}
for key, mod in list(tcl_modules.items()):
print(key)
if key != 'tclCommands.TclCommand':
class_name = key.split('.')[1]
class_type = getattr(mod, class_name)