Merge branch 'master' of https://bitbucket.org/jpcgt/flatcam
This commit is contained in:
commit
3f7e4a5966
@ -1,5 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
from PyQt4 import QtCore
|
||||||
from FlatCAMApp import App
|
from FlatCAMApp import App
|
||||||
|
|
||||||
def debug_trace():
|
def debug_trace():
|
||||||
@ -10,6 +11,10 @@ def debug_trace():
|
|||||||
#set_trace()
|
#set_trace()
|
||||||
|
|
||||||
debug_trace()
|
debug_trace()
|
||||||
|
|
||||||
|
# all X11 calling should be thread safe otherwise we have strange issues
|
||||||
|
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
|
||||||
|
|
||||||
app = QtGui.QApplication(sys.argv)
|
app = QtGui.QApplication(sys.argv)
|
||||||
fc = App()
|
fc = App()
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec_())
|
250
FlatCAMApp.py
250
FlatCAMApp.py
@ -1,4 +1,4 @@
|
|||||||
import sys
|
import sys, traceback
|
||||||
import urllib
|
import urllib
|
||||||
import getopt
|
import getopt
|
||||||
import random
|
import random
|
||||||
@ -27,7 +27,7 @@ from FlatCAMDraw import FlatCAMDraw
|
|||||||
from FlatCAMProcess import *
|
from FlatCAMProcess import *
|
||||||
from MeasurementTool import Measurement
|
from MeasurementTool import Measurement
|
||||||
from DblSidedTool import DblSidedTool
|
from DblSidedTool import DblSidedTool
|
||||||
|
import tclCommands
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
## App ##
|
## App ##
|
||||||
@ -107,6 +107,9 @@ class App(QtCore.QObject):
|
|||||||
|
|
||||||
message = QtCore.pyqtSignal(str, str, str)
|
message = QtCore.pyqtSignal(str, str, str)
|
||||||
|
|
||||||
|
# Emmited when shell command is finished(one command only)
|
||||||
|
shell_command_finished = QtCore.pyqtSignal(object)
|
||||||
|
|
||||||
# Emitted when an unhandled exception happens
|
# Emitted when an unhandled exception happens
|
||||||
# in the worker task.
|
# in the worker task.
|
||||||
thread_exception = QtCore.pyqtSignal(object)
|
thread_exception = QtCore.pyqtSignal(object)
|
||||||
@ -283,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,
|
||||||
@ -528,8 +533,8 @@ class App(QtCore.QObject):
|
|||||||
self.shell.resize(*self.defaults["shell_shape"])
|
self.shell.resize(*self.defaults["shell_shape"])
|
||||||
self.shell.append_output("FlatCAM %s\n(c) 2014-2015 Juan Pablo Caram\n\n" % self.version)
|
self.shell.append_output("FlatCAM %s\n(c) 2014-2015 Juan Pablo Caram\n\n" % self.version)
|
||||||
self.shell.append_output("Type help to get started.\n\n")
|
self.shell.append_output("Type help to get started.\n\n")
|
||||||
self.tcl = Tkinter.Tcl()
|
|
||||||
self.setup_shell()
|
self.init_tcl()
|
||||||
|
|
||||||
self.ui.shell_dock = QtGui.QDockWidget("FlatCAM TCL Shell")
|
self.ui.shell_dock = QtGui.QDockWidget("FlatCAM TCL Shell")
|
||||||
self.ui.shell_dock.setWidget(self.shell)
|
self.ui.shell_dock.setWidget(self.shell)
|
||||||
@ -559,6 +564,17 @@ class App(QtCore.QObject):
|
|||||||
|
|
||||||
App.log.debug("END of constructor. Releasing control.")
|
App.log.debug("END of constructor. Releasing control.")
|
||||||
|
|
||||||
|
def init_tcl(self):
|
||||||
|
if hasattr(self,'tcl'):
|
||||||
|
# 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 = Tkinter.Tcl()
|
||||||
|
self.setup_shell()
|
||||||
|
|
||||||
def defaults_read_form(self):
|
def defaults_read_form(self):
|
||||||
for option in self.defaults_form_fields:
|
for option in self.defaults_form_fields:
|
||||||
self.defaults[option] = self.defaults_form_fields[option].get_value()
|
self.defaults[option] = self.defaults_form_fields[option].get_value()
|
||||||
@ -661,36 +677,111 @@ class App(QtCore.QObject):
|
|||||||
else:
|
else:
|
||||||
self.defaults['stats'][resource] = 1
|
self.defaults['stats'][resource] = 1
|
||||||
|
|
||||||
def raiseTclError(self, text):
|
class TclErrorException(Exception):
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
: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
|
this method pass exception from python into TCL as error, so we get stacktrace and reason
|
||||||
:param text: text of error
|
:param text: text of error
|
||||||
:return: raise exception
|
:return: raise exception
|
||||||
"""
|
"""
|
||||||
self.tcl.eval('return -code error "%s"' % text)
|
|
||||||
raise Exception(text)
|
self.display_tcl_error(text)
|
||||||
|
raise self.TclErrorException(text)
|
||||||
|
|
||||||
def exec_command(self, text):
|
def exec_command(self, text):
|
||||||
"""
|
"""
|
||||||
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
|
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
|
||||||
|
Also handles execution in separated threads
|
||||||
|
|
||||||
|
:param text:
|
||||||
|
:return: output if there was any
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.report_usage('exec_command')
|
||||||
|
|
||||||
|
result = self.exec_command_test(text, False)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def exec_command_test(self, text, reraise=True):
|
||||||
|
"""
|
||||||
|
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
|
||||||
|
|
||||||
:param text: Input command
|
:param text: Input command
|
||||||
:return: None
|
:param reraise: raise exception and not hide it, used mainly in unittests
|
||||||
|
:return: output if there was any
|
||||||
"""
|
"""
|
||||||
self.report_usage('exec_command')
|
|
||||||
|
|
||||||
text = str(text)
|
text = str(text)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
self.shell.open_proccessing()
|
||||||
result = self.tcl.eval(str(text))
|
result = self.tcl.eval(str(text))
|
||||||
|
if result!='None':
|
||||||
self.shell.append_output(result + '\n')
|
self.shell.append_output(result + '\n')
|
||||||
except Tkinter.TclError, e:
|
except Tkinter.TclError, e:
|
||||||
#this will display more precise answer if something in TCL shell fail
|
#this will display more precise answer if something in TCL shell fail
|
||||||
result = self.tcl.eval("set errorInfo")
|
result = self.tcl.eval("set errorInfo")
|
||||||
self.log.error("Exec command Exception: %s" % (result + '\n'))
|
self.log.error("Exec command Exception: %s" % (result + '\n'))
|
||||||
self.shell.append_error('ERROR: ' + result + '\n')
|
self.shell.append_error('ERROR: ' + result + '\n')
|
||||||
#show error in console and just return
|
#show error in console and just return or in test raise exception
|
||||||
return
|
if reraise:
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
self.shell.close_proccessing()
|
||||||
|
pass
|
||||||
|
return result
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Code below is unsused. Saved for later.
|
Code below is unsused. Saved for later.
|
||||||
@ -1024,6 +1115,7 @@ class App(QtCore.QObject):
|
|||||||
toggle shell if is visible close it if closed open it
|
toggle shell if is visible close it if closed open it
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.ui.shell_dock.isVisible():
|
if self.ui.shell_dock.isVisible():
|
||||||
self.ui.shell_dock.hide()
|
self.ui.shell_dock.hide()
|
||||||
else:
|
else:
|
||||||
@ -1036,6 +1128,7 @@ class App(QtCore.QObject):
|
|||||||
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
objs = self.collection.get_selected()
|
objs = self.collection.get_selected()
|
||||||
|
|
||||||
def initialize(obj, app):
|
def initialize(obj, app):
|
||||||
@ -1483,6 +1576,9 @@ class App(QtCore.QObject):
|
|||||||
|
|
||||||
self.plotcanvas.clear()
|
self.plotcanvas.clear()
|
||||||
|
|
||||||
|
# tcl needs to be reinitialized, otherwise old shell variables etc remains
|
||||||
|
self.init_tcl()
|
||||||
|
|
||||||
self.collection.delete_all()
|
self.collection.delete_all()
|
||||||
|
|
||||||
self.setup_component_editor()
|
self.setup_component_editor()
|
||||||
@ -1744,6 +1840,7 @@ class App(QtCore.QObject):
|
|||||||
:param outname:
|
:param outname:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.log.debug("export_svg()")
|
self.log.debug("export_svg()")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -2409,14 +2506,18 @@ class App(QtCore.QObject):
|
|||||||
return 'Ok'
|
return 'Ok'
|
||||||
|
|
||||||
|
|
||||||
def geocutout(name, *args):
|
def geocutout(name=None, *args):
|
||||||
"""
|
'''
|
||||||
|
TCL shell command - see help section
|
||||||
|
|
||||||
Subtract gaps from geometry, this will not create new object
|
Subtract gaps from geometry, this will not create new object
|
||||||
|
|
||||||
:param name:
|
:param name: name of object
|
||||||
:param args:
|
:param args: array of arguments
|
||||||
:return:
|
:return: "Ok" if completed without errors
|
||||||
"""
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
a, kwa = h(*args)
|
a, kwa = h(*args)
|
||||||
types = {'dia': float,
|
types = {'dia': float,
|
||||||
'gapsize': float,
|
'gapsize': float,
|
||||||
@ -2430,15 +2531,21 @@ class App(QtCore.QObject):
|
|||||||
# 2tb - 2*top + 2*bottom
|
# 2tb - 2*top + 2*bottom
|
||||||
# 8 - 2*left + 2*right +2*top + 2*bottom
|
# 8 - 2*left + 2*right +2*top + 2*bottom
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
self.raise_tcl_error('Argument name is missing.')
|
||||||
|
|
||||||
for key in kwa:
|
for key in kwa:
|
||||||
if key not in types:
|
if key not in types:
|
||||||
return 'Unknown parameter: %s' % key
|
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||||
|
try:
|
||||||
kwa[key] = types[key](kwa[key])
|
kwa[key] = types[key](kwa[key])
|
||||||
|
except Exception, e:
|
||||||
|
self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = self.collection.get_by_name(str(name))
|
obj = self.collection.get_by_name(str(name))
|
||||||
except:
|
except:
|
||||||
return "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
|
# Get min and max data for each object as we just cut rectangles across X or Y
|
||||||
xmin, ymin, xmax, ymax = obj.bounds()
|
xmin, ymin, xmax, ymax = obj.bounds()
|
||||||
@ -2487,7 +2594,8 @@ class App(QtCore.QObject):
|
|||||||
px + gapsize,
|
px + gapsize,
|
||||||
ymax + gapsize)
|
ymax + gapsize)
|
||||||
|
|
||||||
return 'Ok'
|
except Exception as unknown:
|
||||||
|
self.raise_tcl_unknown_error(unknown)
|
||||||
|
|
||||||
def mirror(name, *args):
|
def mirror(name, *args):
|
||||||
a, kwa = h(*args)
|
a, kwa = h(*args)
|
||||||
@ -2761,6 +2869,8 @@ class App(QtCore.QObject):
|
|||||||
:param args: array of arguments
|
:param args: array of arguments
|
||||||
:return: "Ok" if completed without errors
|
:return: "Ok" if completed without errors
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
a, kwa = h(*args)
|
a, kwa = h(*args)
|
||||||
types = {'tools': str,
|
types = {'tools': str,
|
||||||
'outname': str,
|
'outname': str,
|
||||||
@ -2772,26 +2882,26 @@ class App(QtCore.QObject):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
self.raiseTclError('Argument name is missing.')
|
self.raise_tcl_error('Argument name is missing.')
|
||||||
|
|
||||||
for key in kwa:
|
for key in kwa:
|
||||||
if key not in types:
|
if key not in types:
|
||||||
self.raiseTclError('Unknown parameter: %s' % key)
|
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||||
try:
|
try:
|
||||||
kwa[key] = types[key](kwa[key])
|
kwa[key] = types[key](kwa[key])
|
||||||
except Exception, e:
|
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:
|
try:
|
||||||
obj = self.collection.get_by_name(str(name))
|
obj = self.collection.get_by_name(str(name))
|
||||||
except:
|
except:
|
||||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||||
|
|
||||||
if obj is None:
|
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):
|
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:
|
try:
|
||||||
# Get the tools from the list
|
# Get the tools from the list
|
||||||
@ -2811,9 +2921,11 @@ class App(QtCore.QObject):
|
|||||||
obj.app.new_object("cncjob", job_name, job_init)
|
obj.app.new_object("cncjob", job_name, job_init)
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.raiseTclError("Operation failed: %s" % str(e))
|
self.raise_tcl_error("Operation failed: %s" % str(e))
|
||||||
|
|
||||||
|
except Exception as unknown:
|
||||||
|
self.raise_tcl_unknown_error(unknown)
|
||||||
|
|
||||||
return 'Ok'
|
|
||||||
|
|
||||||
def millholes(name=None, *args):
|
def millholes(name=None, *args):
|
||||||
'''
|
'''
|
||||||
@ -2822,48 +2934,51 @@ class App(QtCore.QObject):
|
|||||||
:param args: array of arguments
|
:param args: array of arguments
|
||||||
:return: "Ok" if completed without errors
|
:return: "Ok" if completed without errors
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
a, kwa = h(*args)
|
a, kwa = h(*args)
|
||||||
types = {'tooldia': float,
|
types = {'tooldia': float,
|
||||||
'tools': str,
|
'tools': str,
|
||||||
'outname': str}
|
'outname': str}
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
self.raiseTclError('Argument name is missing.')
|
self.raise_tcl_error('Argument name is missing.')
|
||||||
|
|
||||||
for key in kwa:
|
for key in kwa:
|
||||||
if key not in types:
|
if key not in types:
|
||||||
self.raiseTclError('Unknown parameter: %s' % key)
|
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||||
try:
|
try:
|
||||||
kwa[key] = types[key](kwa[key])
|
kwa[key] = types[key](kwa[key])
|
||||||
except Exception, e:
|
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:
|
try:
|
||||||
if 'tools' in kwa:
|
if 'tools' in kwa:
|
||||||
kwa['tools'] = [x.strip() for x in kwa['tools'].split(",")]
|
kwa['tools'] = [x.strip() for x in kwa['tools'].split(",")]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.raiseTclError("Bad tools: %s" % str(e))
|
self.raise_tcl_error("Bad tools: %s" % str(e))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = self.collection.get_by_name(str(name))
|
obj = self.collection.get_by_name(str(name))
|
||||||
except:
|
except:
|
||||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||||
|
|
||||||
if obj is None:
|
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):
|
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:
|
try:
|
||||||
success, msg = obj.generate_milling(**kwa)
|
success, msg = obj.generate_milling(**kwa)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.raiseTclError("Operation failed: %s" % str(e))
|
self.raise_tcl_error("Operation failed: %s" % str(e))
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
self.raiseTclError(msg)
|
self.raise_tcl_error(msg)
|
||||||
|
|
||||||
return 'Ok'
|
except Exception as unknown:
|
||||||
|
self.raise_tcl_unknown_error(unknown)
|
||||||
|
|
||||||
def exteriors(name=None, *args):
|
def exteriors(name=None, *args):
|
||||||
'''
|
'''
|
||||||
@ -2872,30 +2987,32 @@ class App(QtCore.QObject):
|
|||||||
:param args: array of arguments
|
:param args: array of arguments
|
||||||
:return: "Ok" if completed without errors
|
:return: "Ok" if completed without errors
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
a, kwa = h(*args)
|
a, kwa = h(*args)
|
||||||
types = {'outname': str}
|
types = {'outname': str}
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
self.raiseTclError('Argument name is missing.')
|
self.raise_tcl_error('Argument name is missing.')
|
||||||
|
|
||||||
for key in kwa:
|
for key in kwa:
|
||||||
if key not in types:
|
if key not in types:
|
||||||
self.raiseTclError('Unknown parameter: %s' % key)
|
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||||
try:
|
try:
|
||||||
kwa[key] = types[key](kwa[key])
|
kwa[key] = types[key](kwa[key])
|
||||||
except Exception, e:
|
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:
|
try:
|
||||||
obj = self.collection.get_by_name(str(name))
|
obj = self.collection.get_by_name(str(name))
|
||||||
except:
|
except:
|
||||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||||
|
|
||||||
if obj is None:
|
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):
|
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):
|
def geo_init(geo_obj, app_obj):
|
||||||
geo_obj.solid_geometry = obj_exteriors
|
geo_obj.solid_geometry = obj_exteriors
|
||||||
@ -2909,9 +3026,10 @@ class App(QtCore.QObject):
|
|||||||
obj_exteriors = obj.get_exteriors()
|
obj_exteriors = obj.get_exteriors()
|
||||||
self.new_object('geometry', outname, geo_init)
|
self.new_object('geometry', outname, geo_init)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.raiseTclError("Failed: %s" % str(e))
|
self.raise_tcl_error("Failed: %s" % str(e))
|
||||||
|
|
||||||
return 'Ok'
|
except Exception as unknown:
|
||||||
|
self.raise_tcl_unknown_error(unknown)
|
||||||
|
|
||||||
def interiors(name=None, *args):
|
def interiors(name=None, *args):
|
||||||
'''
|
'''
|
||||||
@ -2920,30 +3038,32 @@ class App(QtCore.QObject):
|
|||||||
:param args: array of arguments
|
:param args: array of arguments
|
||||||
:return: "Ok" if completed without errors
|
:return: "Ok" if completed without errors
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
a, kwa = h(*args)
|
a, kwa = h(*args)
|
||||||
types = {'outname': str}
|
types = {'outname': str}
|
||||||
|
|
||||||
for key in kwa:
|
for key in kwa:
|
||||||
if key not in types:
|
if key not in types:
|
||||||
self.raiseTclError('Unknown parameter: %s' % key)
|
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||||
try:
|
try:
|
||||||
kwa[key] = types[key](kwa[key])
|
kwa[key] = types[key](kwa[key])
|
||||||
except Exception, e:
|
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:
|
if name is None:
|
||||||
self.raiseTclError('Argument name is missing.')
|
self.raise_tcl_error('Argument name is missing.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = self.collection.get_by_name(str(name))
|
obj = self.collection.get_by_name(str(name))
|
||||||
except:
|
except:
|
||||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||||
|
|
||||||
if obj is None:
|
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):
|
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):
|
def geo_init(geo_obj, app_obj):
|
||||||
geo_obj.solid_geometry = obj_interiors
|
geo_obj.solid_geometry = obj_interiors
|
||||||
@ -2957,9 +3077,10 @@ class App(QtCore.QObject):
|
|||||||
obj_interiors = obj.get_interiors()
|
obj_interiors = obj.get_interiors()
|
||||||
self.new_object('geometry', outname, geo_init)
|
self.new_object('geometry', outname, geo_init)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.raiseTclError("Failed: %s" % str(e))
|
self.raise_tcl_error("Failed: %s" % str(e))
|
||||||
|
|
||||||
return 'Ok'
|
except Exception as unknown:
|
||||||
|
self.raise_tcl_unknown_error(unknown)
|
||||||
|
|
||||||
def isolate(name=None, *args):
|
def isolate(name=None, *args):
|
||||||
'''
|
'''
|
||||||
@ -2977,29 +3098,29 @@ class App(QtCore.QObject):
|
|||||||
|
|
||||||
for key in kwa:
|
for key in kwa:
|
||||||
if key not in types:
|
if key not in types:
|
||||||
self.raiseTclError('Unknown parameter: %s' % key)
|
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||||
try:
|
try:
|
||||||
kwa[key] = types[key](kwa[key])
|
kwa[key] = types[key](kwa[key])
|
||||||
except Exception, e:
|
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:
|
try:
|
||||||
obj = self.collection.get_by_name(str(name))
|
obj = self.collection.get_by_name(str(name))
|
||||||
except:
|
except:
|
||||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||||
|
|
||||||
if obj is None:
|
if obj is None:
|
||||||
self.raiseTclError("Object not found: %s" % name)
|
self.raise_tcl_error("Object not found: %s" % name)
|
||||||
|
|
||||||
assert isinstance(obj, FlatCAMGerber), \
|
assert isinstance(obj, FlatCAMGerber), \
|
||||||
"Expected a FlatCAMGerber, got %s" % type(obj)
|
"Expected a FlatCAMGerber, got %s" % type(obj)
|
||||||
|
|
||||||
if not isinstance(obj, FlatCAMGerber):
|
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:
|
try:
|
||||||
obj.isolate(**kwa)
|
obj.isolate(**kwa)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.raiseTclError("Operation failed: %s" % str(e))
|
self.raise_tcl_error("Operation failed: %s" % str(e))
|
||||||
|
|
||||||
return 'Ok'
|
return 'Ok'
|
||||||
|
|
||||||
@ -3390,11 +3511,11 @@ class App(QtCore.QObject):
|
|||||||
Test it like this:
|
Test it like this:
|
||||||
if name is None:
|
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.
|
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.
|
which is catched in exec_command and displayed in TCL shell console with red background.
|
||||||
Error in console is displayed with TCL trace.
|
Error in console is displayed with TCL trace.
|
||||||
|
|
||||||
@ -3776,6 +3897,9 @@ class App(QtCore.QObject):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#import/overwrite tcl commands as objects of TclCommand descendants
|
||||||
|
tclCommands.register_all_commands(self, commands)
|
||||||
|
|
||||||
# Add commands to the tcl interpreter
|
# Add commands to the tcl interpreter
|
||||||
for cmd in commands:
|
for cmd in commands:
|
||||||
self.tcl.createcommand(cmd, commands[cmd]['fcn'])
|
self.tcl.createcommand(cmd, commands[cmd]['fcn'])
|
||||||
|
@ -1040,6 +1040,10 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
|||||||
|
|
||||||
self.app.inform.emit("Saved to: " + filename)
|
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):
|
def on_plot_cb_click(self, *args):
|
||||||
if self.muted_ui:
|
if self.muted_ui:
|
||||||
return
|
return
|
||||||
@ -1243,7 +1247,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||||||
outname=None,
|
outname=None,
|
||||||
spindlespeed=None,
|
spindlespeed=None,
|
||||||
multidepth=None,
|
multidepth=None,
|
||||||
depthperpass=None):
|
depthperpass=None,
|
||||||
|
use_thread=True):
|
||||||
"""
|
"""
|
||||||
Creates a CNCJob out of this Geometry object. The actual
|
Creates a CNCJob out of this Geometry object. The actual
|
||||||
work is done by the target FlatCAMCNCjob object's
|
work is done by the target FlatCAMCNCjob object's
|
||||||
@ -1304,6 +1309,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||||||
|
|
||||||
app_obj.progress.emit(80)
|
app_obj.progress.emit(80)
|
||||||
|
|
||||||
|
|
||||||
|
if use_thread:
|
||||||
# To be run in separate thread
|
# To be run in separate thread
|
||||||
def job_thread(app_obj):
|
def job_thread(app_obj):
|
||||||
with self.app.proc_container.new("Generating CNC Job."):
|
with self.app.proc_container.new("Generating CNC Job."):
|
||||||
@ -1316,6 +1323,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||||||
|
|
||||||
# Send to worker
|
# Send to worker
|
||||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
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
|
def on_plot_cb_click(self, *args): # TODO: args not needed
|
||||||
if self.muted_ui:
|
if self.muted_ui:
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
#import FlatCAMApp
|
|
||||||
|
|
||||||
|
|
||||||
class Worker(QtCore.QObject):
|
class Worker(QtCore.QObject):
|
||||||
@ -8,15 +7,34 @@ class Worker(QtCore.QObject):
|
|||||||
in a single independent thread.
|
in a single independent thread.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# avoid multiple tests for debug availability
|
||||||
|
pydevd_failed = False
|
||||||
|
|
||||||
def __init__(self, app, name=None):
|
def __init__(self, app, name=None):
|
||||||
super(Worker, self).__init__()
|
super(Worker, self).__init__()
|
||||||
self.app = app
|
self.app = app
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
def allow_debug(self):
|
||||||
|
"""
|
||||||
|
allow debuging/breakpoints in this threads
|
||||||
|
should work from PyCharm and PyDev
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.pydevd_failed:
|
||||||
|
try:
|
||||||
|
import pydevd
|
||||||
|
pydevd.settrace(suspend=False, trace_only_current_thread=True)
|
||||||
|
except ImportError:
|
||||||
|
self.pydevd_failed=True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
self.app.log.debug("Worker Started!")
|
self.app.log.debug("Worker Started!")
|
||||||
|
|
||||||
|
self.allow_debug()
|
||||||
|
|
||||||
# Tasks are queued in the event listener.
|
# Tasks are queued in the event listener.
|
||||||
self.app.worker_task.connect(self.do_worker_task)
|
self.app.worker_task.connect(self.do_worker_task)
|
||||||
|
|
||||||
@ -24,8 +42,8 @@ class Worker(QtCore.QObject):
|
|||||||
|
|
||||||
self.app.log.debug("Running task: %s" % str(task))
|
self.app.log.debug("Running task: %s" % str(task))
|
||||||
|
|
||||||
# 'worker_name' property of task allows to target
|
self.allow_debug()
|
||||||
# specific worker.
|
|
||||||
if ('worker_name' in task and task['worker_name'] == self.name) or \
|
if ('worker_name' in task and task['worker_name'] == self.name) or \
|
||||||
('worker_name' not in task and self.name is None):
|
('worker_name' not in task and self.name is None):
|
||||||
|
|
||||||
@ -37,5 +55,4 @@ class Worker(QtCore.QObject):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# FlatCAMApp.App.log.debug("Task ignored.")
|
|
||||||
self.app.log.debug("Task ignored.")
|
self.app.log.debug("Task ignored.")
|
25
camlib.py
25
camlib.py
@ -136,6 +136,27 @@ class Geometry(object):
|
|||||||
log.error("Failed to run union on polygons.")
|
log.error("Failed to run union on polygons.")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def add_polyline(self, points):
|
||||||
|
"""
|
||||||
|
Adds a polyline to the object (by union)
|
||||||
|
|
||||||
|
:param points: The vertices of the polyline.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
if self.solid_geometry is None:
|
||||||
|
self.solid_geometry = []
|
||||||
|
|
||||||
|
if type(self.solid_geometry) is list:
|
||||||
|
self.solid_geometry.append(LineString(points))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.solid_geometry = self.solid_geometry.union(LineString(points))
|
||||||
|
except:
|
||||||
|
#print "Failed to run union on polygons."
|
||||||
|
log.error("Failed to run union on polylines.")
|
||||||
|
raise
|
||||||
|
|
||||||
def subtract_polygon(self, points):
|
def subtract_polygon(self, points):
|
||||||
"""
|
"""
|
||||||
Subtract polygon from the given object. This only operates on the paths in the original geometry, i.e. it converts polygons into paths.
|
Subtract polygon from the given object. This only operates on the paths in the original geometry, i.e. it converts polygons into paths.
|
||||||
@ -2756,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 ','
|
||||||
@ -2797,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
|
||||||
|
BIN
camlib.pyc
BIN
camlib.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
394
tclCommands/TclCommand.py
Normal file
394
tclCommands/TclCommand.py
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import FlatCAMApp
|
||||||
|
import abc
|
||||||
|
import collections
|
||||||
|
from PyQt4 import QtCore
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommand(object):
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# 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': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# original incoming arguments into command
|
||||||
|
original_args = None
|
||||||
|
|
||||||
|
def __init__(self, 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_current_command(self):
|
||||||
|
"""
|
||||||
|
get current command, we are not able to get it from TCL we have to reconstruct it
|
||||||
|
:return: current command
|
||||||
|
"""
|
||||||
|
command_string = []
|
||||||
|
command_string.append(self.aliases[0])
|
||||||
|
if self.original_args is not None:
|
||||||
|
for arg in self.original_args:
|
||||||
|
command_string.append(arg)
|
||||||
|
return " ".join(command_string)
|
||||||
|
|
||||||
|
def get_decorated_help(self):
|
||||||
|
"""
|
||||||
|
Decorate help for TCL console output.
|
||||||
|
|
||||||
|
:return: decorated help from structure
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_decorated_command(alias_name):
|
||||||
|
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(help_key, help_text, in_command=False):
|
||||||
|
option_symbol = ''
|
||||||
|
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 help_key in self.option_types:
|
||||||
|
option_symbol = '-'
|
||||||
|
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 + help_key + " <" + type_name + ">"
|
||||||
|
|
||||||
|
if in_command:
|
||||||
|
if help_key in self.required:
|
||||||
|
return in_command_name
|
||||||
|
else:
|
||||||
|
return '[' + in_command_name + "]"
|
||||||
|
else:
|
||||||
|
if help_key in self.required:
|
||||||
|
return "\t" + option_symbol + help_key + " <" + type_name + ">: " + help_text
|
||||||
|
else:
|
||||||
|
return "\t[" + option_symbol + help_key + " <" + type_name + ">: " + help_text + "]"
|
||||||
|
|
||||||
|
def get_decorated_example(example_item):
|
||||||
|
return "> "+example_item
|
||||||
|
|
||||||
|
help_string = [self.help['main']]
|
||||||
|
for alias in self.aliases:
|
||||||
|
help_string.append(get_decorated_command(alias))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
return "\n".join(help_string)
|
||||||
|
|
||||||
|
@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 accessibility,
|
||||||
|
original should be removed after all commands will be converted
|
||||||
|
|
||||||
|
:param args: arguments from tcl to parse
|
||||||
|
:return: arguments, options
|
||||||
|
"""
|
||||||
|
|
||||||
|
options = {}
|
||||||
|
arguments = []
|
||||||
|
n = len(args)
|
||||||
|
name = None
|
||||||
|
for i in range(n):
|
||||||
|
match = re.search(r'^-([a-zA-Z].*)', args[i])
|
||||||
|
if match:
|
||||||
|
assert name is None
|
||||||
|
name = match.group(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
arguments.append(args[i])
|
||||||
|
else:
|
||||||
|
options[name] = args[i]
|
||||||
|
name = None
|
||||||
|
|
||||||
|
return arguments, options
|
||||||
|
|
||||||
|
def check_args(self, args):
|
||||||
|
"""
|
||||||
|
Check arguments and options for right types
|
||||||
|
|
||||||
|
:param args: arguments from tcl to check
|
||||||
|
:return: named_args, unnamed_args
|
||||||
|
"""
|
||||||
|
|
||||||
|
arguments, options = self.parse_arguments(args)
|
||||||
|
|
||||||
|
named_args = {}
|
||||||
|
unnamed_args = []
|
||||||
|
|
||||||
|
# check arguments
|
||||||
|
idx = 0
|
||||||
|
arg_names_items = self.arg_names.items()
|
||||||
|
for argument in arguments:
|
||||||
|
if len(self.arg_names) > idx:
|
||||||
|
key, arg_type = arg_names_items[idx]
|
||||||
|
try:
|
||||||
|
named_args[key] = arg_type(argument)
|
||||||
|
except Exception, e:
|
||||||
|
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 options
|
||||||
|
for key in options:
|
||||||
|
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)))
|
||||||
|
|
||||||
|
# check required arguments
|
||||||
|
for key in self.required:
|
||||||
|
if key not in named_args:
|
||||||
|
self.raise_tcl_error("Missing required argument '%s'." % key)
|
||||||
|
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
#self.worker_task.emit({'fcn': self.exec_command_test, 'params': [text, False]})
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.log.debug("TCL command '%s' executed." % str(self.__class__))
|
||||||
|
self.original_args=args
|
||||||
|
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.display_tcl_error(unknown, error_info)
|
||||||
|
self.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.
|
||||||
|
No main catch should be implemented here.
|
||||||
|
|
||||||
|
:param args: array of known named arguments and options
|
||||||
|
:param unnamed_args: array of other values which were passed into command
|
||||||
|
without -somename and we do not have them in known arg_names
|
||||||
|
:return: None, output text or exception
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError("Please Implement this method")
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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=300000):
|
||||||
|
"""Block loop until signal emitted, or timeout (ms) elapses."""
|
||||||
|
loop = QtCore.QEventLoop()
|
||||||
|
|
||||||
|
# Normal termination
|
||||||
|
signal.connect(loop.quit)
|
||||||
|
|
||||||
|
# Termination by exception in thread
|
||||||
|
self.app.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:
|
||||||
|
raise ex[0]
|
||||||
|
|
||||||
|
if status['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__))
|
||||||
|
self.original_args=args
|
||||||
|
args, unnamed_args = self.check_args(args)
|
||||||
|
if 'timeout' in args:
|
||||||
|
passed_timeout=args['timeout']
|
||||||
|
del args['timeout']
|
||||||
|
else:
|
||||||
|
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())
|
||||||
|
|
||||||
|
def handle_finished(obj):
|
||||||
|
self.app.shell_command_finished.disconnect(handle_finished)
|
||||||
|
if self.error is not None:
|
||||||
|
self.raise_tcl_unknown_error(self.error)
|
||||||
|
|
||||||
|
self.app.shell_command_finished.connect(handle_finished)
|
||||||
|
|
||||||
|
with wait_signal(self.app.shell_command_finished, 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
|
||||||
|
self.app.worker_task.emit({'fcn': self.execute_call, 'params': [args, unnamed_args]})
|
||||||
|
|
||||||
|
return self.output
|
||||||
|
|
||||||
|
except Exception as unknown:
|
||||||
|
error_info=sys.exc_info()
|
||||||
|
self.log.error("TCL command '%s' failed." % str(self))
|
||||||
|
self.app.display_tcl_error(unknown, error_info)
|
||||||
|
self.raise_tcl_unknown_error(unknown)
|
61
tclCommands/TclCommandAddPolygon.py
Normal file
61
tclCommands/TclCommandAddPolygon.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from ObjectCollection import *
|
||||||
|
import TclCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommandAddPolygon(TclCommand.TclCommandSignaled):
|
||||||
|
"""
|
||||||
|
Tcl shell command to create a polygon in the given Geometry object
|
||||||
|
"""
|
||||||
|
|
||||||
|
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
|
||||||
|
aliases = ['add_polygon', 'add_poly']
|
||||||
|
|
||||||
|
# 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 = ['name']
|
||||||
|
|
||||||
|
# structured help for current command, args needs to be ordered
|
||||||
|
help = {
|
||||||
|
'main': "Creates a polygon in the given Geometry object.",
|
||||||
|
'args': collections.OrderedDict([
|
||||||
|
('name', 'Name of the Geometry object to which to append the polygon.'),
|
||||||
|
('xi, yi', 'Coordinates of points in the polygon.')
|
||||||
|
]),
|
||||||
|
'examples': [
|
||||||
|
'add_polygon <name> <x0> <y0> <x1> <y1> <x2> <y2> [x3 y3 [...]]'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
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, Geometry):
|
||||||
|
self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
|
||||||
|
|
||||||
|
if len(unnamed_args) % 2 != 0:
|
||||||
|
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()
|
61
tclCommands/TclCommandAddPolyline.py
Normal file
61
tclCommands/TclCommandAddPolyline.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from ObjectCollection import *
|
||||||
|
import TclCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommandAddPolyline(TclCommand.TclCommandSignaled):
|
||||||
|
"""
|
||||||
|
Tcl shell command to create a polyline in the given Geometry object
|
||||||
|
"""
|
||||||
|
|
||||||
|
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
|
||||||
|
aliases = ['add_polyline']
|
||||||
|
|
||||||
|
# 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 = ['name']
|
||||||
|
|
||||||
|
# structured help for current command, args needs to be ordered
|
||||||
|
help = {
|
||||||
|
'main': "Creates a polyline in the given Geometry object.",
|
||||||
|
'args': collections.OrderedDict([
|
||||||
|
('name', 'Name of the Geometry object to which to append the polyline.'),
|
||||||
|
('xi, yi', 'Coordinates of points in the polyline.')
|
||||||
|
]),
|
||||||
|
'examples': [
|
||||||
|
'add_polyline <name> <x0> <y0> <x1> <y1> <x2> <y2> [x3 y3 [...]]'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
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, Geometry):
|
||||||
|
self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
|
||||||
|
|
||||||
|
if len(unnamed_args) % 2 != 0:
|
||||||
|
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()
|
80
tclCommands/TclCommandCncjob.py
Normal file
80
tclCommands/TclCommandCncjob.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
from ObjectCollection import *
|
||||||
|
import TclCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommandCncjob(TclCommand.TclCommandSignaled):
|
||||||
|
"""
|
||||||
|
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.')
|
||||||
|
]),
|
||||||
|
'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, FlatCAMGeometry):
|
||||||
|
self.raise_tcl_error('Expected FlatCAMGeometry, got %s %s.' % (name, type(obj)))
|
||||||
|
|
||||||
|
del args['name']
|
||||||
|
obj.generatecncjob(use_thread = False, **args)
|
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)
|
79
tclCommands/TclCommandExportGcode.py
Normal file
79
tclCommands/TclCommandExportGcode.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
from ObjectCollection import *
|
||||||
|
import TclCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommandExportGcode(TclCommand.TclCommandSignaled):
|
||||||
|
"""
|
||||||
|
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)
|
64
tclCommands/TclCommandExteriors.py
Normal file
64
tclCommands/TclCommandExteriors.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
from ObjectCollection import *
|
||||||
|
import TclCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommandExteriors(TclCommand.TclCommandSignaled):
|
||||||
|
"""
|
||||||
|
Tcl shell command to get exteriors of polygons
|
||||||
|
"""
|
||||||
|
|
||||||
|
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
|
||||||
|
aliases = ['exteriors', 'ext']
|
||||||
|
|
||||||
|
# dictionary of types from Tcl command, 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([
|
||||||
|
('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': "Get exteriors of polygons.",
|
||||||
|
'args': collections.OrderedDict([
|
||||||
|
('name', 'Name of the source Geometry object.'),
|
||||||
|
('outname', 'Name of the resulting Geometry object.')
|
||||||
|
]),
|
||||||
|
'examples': []
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute(self, args, unnamed_args):
|
||||||
|
"""
|
||||||
|
execute current TCL shell command
|
||||||
|
|
||||||
|
:param args: array of known named arguments and options
|
||||||
|
:param unnamed_args: array of other values which were passed into command
|
||||||
|
without -somename and we do not have them in known arg_names
|
||||||
|
:return: None or exception
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = args['name']
|
||||||
|
|
||||||
|
if 'outname' in args:
|
||||||
|
outname = args['outname']
|
||||||
|
else:
|
||||||
|
outname = name + "_exteriors"
|
||||||
|
|
||||||
|
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, Geometry):
|
||||||
|
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
|
||||||
|
|
||||||
|
obj_exteriors = obj.get_exteriors()
|
||||||
|
self.app.new_object('geometry', outname, geo_init)
|
64
tclCommands/TclCommandInteriors.py
Normal file
64
tclCommands/TclCommandInteriors.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
from ObjectCollection import *
|
||||||
|
import TclCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommandInteriors(TclCommand.TclCommandSignaled):
|
||||||
|
"""
|
||||||
|
Tcl shell command to get interiors of polygons
|
||||||
|
"""
|
||||||
|
|
||||||
|
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
|
||||||
|
aliases = ['interiors']
|
||||||
|
|
||||||
|
# dictionary of types from Tcl command, 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([
|
||||||
|
('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': "Get interiors of polygons.",
|
||||||
|
'args': collections.OrderedDict([
|
||||||
|
('name', 'Name of the source Geometry object.'),
|
||||||
|
('outname', 'Name of the resulting Geometry object.')
|
||||||
|
]),
|
||||||
|
'examples': []
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute(self, args, unnamed_args):
|
||||||
|
"""
|
||||||
|
execute current TCL shell command
|
||||||
|
|
||||||
|
:param args: array of known named arguments and options
|
||||||
|
:param unnamed_args: array of other values which were passed into command
|
||||||
|
without -somename and we do not have them in known arg_names
|
||||||
|
:return: None or exception
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = args['name']
|
||||||
|
|
||||||
|
if 'outname' in args:
|
||||||
|
outname = args['outname']
|
||||||
|
else:
|
||||||
|
outname = name + "_interiors"
|
||||||
|
|
||||||
|
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, Geometry):
|
||||||
|
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
|
||||||
|
|
||||||
|
obj_exteriors = obj.get_interiors()
|
||||||
|
self.app.new_object('geometry', outname, geo_init)
|
79
tclCommands/TclCommandIsolate.py
Normal file
79
tclCommands/TclCommandIsolate.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
from ObjectCollection import *
|
||||||
|
import TclCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommandIsolate(TclCommand.TclCommandSignaled):
|
||||||
|
"""
|
||||||
|
Tcl shell command to Creates isolation routing geometry for the given Gerber.
|
||||||
|
|
||||||
|
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 = ['isolate']
|
||||||
|
|
||||||
|
# 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([
|
||||||
|
('dia',float),
|
||||||
|
('passes',int),
|
||||||
|
('overlap',float),
|
||||||
|
('combine',int),
|
||||||
|
('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': "Creates isolation routing geometry for the given Gerber.",
|
||||||
|
'args': collections.OrderedDict([
|
||||||
|
('name', 'Name of the source object.'),
|
||||||
|
('dia', 'Tool diameter.'),
|
||||||
|
('passes', 'Passes of tool width.'),
|
||||||
|
('overlap', 'Fraction of tool diameter to overlap passes.'),
|
||||||
|
('combine', 'Combine all passes into one geometry.'),
|
||||||
|
('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 + "_iso"
|
||||||
|
|
||||||
|
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, FlatCAMGerber):
|
||||||
|
self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (name, type(obj)))
|
||||||
|
|
||||||
|
del args['name']
|
||||||
|
obj.isolate(**args)
|
40
tclCommands/TclCommandNew.py
Normal file
40
tclCommands/TclCommandNew.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from ObjectCollection import *
|
||||||
|
from PyQt4 import QtCore
|
||||||
|
import TclCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommandNew(TclCommand.TclCommand):
|
||||||
|
"""
|
||||||
|
Tcl shell command to starts a new project. Clears objects from memory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
|
||||||
|
aliases = ['new']
|
||||||
|
|
||||||
|
# dictionary of types from Tcl command, needs to be ordered
|
||||||
|
arg_names = collections.OrderedDict()
|
||||||
|
|
||||||
|
# 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': "Starts a new project. Clears objects from memory.",
|
||||||
|
'args': collections.OrderedDict(),
|
||||||
|
'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
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.app.on_file_new()
|
95
tclCommands/TclCommandOpenGerber.py
Normal file
95
tclCommands/TclCommandOpenGerber.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
from ObjectCollection import *
|
||||||
|
import TclCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TclCommandOpenGerber(TclCommand.TclCommandSignaled):
|
||||||
|
"""
|
||||||
|
Tcl shell command to opens a Gerber file
|
||||||
|
"""
|
||||||
|
|
||||||
|
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
|
||||||
|
aliases = ['open_gerber']
|
||||||
|
|
||||||
|
# dictionary of types from Tcl command, needs to be ordered
|
||||||
|
arg_names = collections.OrderedDict([
|
||||||
|
('filename', str)
|
||||||
|
])
|
||||||
|
|
||||||
|
# dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
|
||||||
|
option_types = collections.OrderedDict([
|
||||||
|
('follow', str),
|
||||||
|
('outname', str)
|
||||||
|
])
|
||||||
|
|
||||||
|
# array of mandatory options for current Tcl command: required = {'name','outname'}
|
||||||
|
required = ['filename']
|
||||||
|
|
||||||
|
# structured help for current command, args needs to be ordered
|
||||||
|
help = {
|
||||||
|
'main': "Opens a Gerber file.",
|
||||||
|
'args': collections.OrderedDict([
|
||||||
|
('filename', 'Path to file to open.'),
|
||||||
|
('follow', 'N If 1, does not create polygons, just follows the gerber path.'),
|
||||||
|
('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
|
||||||
|
"""
|
||||||
|
|
||||||
|
# How the object should be initialized
|
||||||
|
def obj_init(gerber_obj, app_obj):
|
||||||
|
|
||||||
|
if not isinstance(gerber_obj, Geometry):
|
||||||
|
self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (outname, type(gerber_obj)))
|
||||||
|
|
||||||
|
# Opening the file happens here
|
||||||
|
self.app.progress.emit(30)
|
||||||
|
try:
|
||||||
|
gerber_obj.parse_file(filename, follow=follow)
|
||||||
|
|
||||||
|
except IOError:
|
||||||
|
app_obj.inform.emit("[error] Failed to open file: %s " % filename)
|
||||||
|
app_obj.progress.emit(0)
|
||||||
|
self.raise_tcl_error('Failed to open file: %s' % filename)
|
||||||
|
|
||||||
|
except ParseError, e:
|
||||||
|
app_obj.inform.emit("[error] Failed to parse file: %s, %s " % (filename, str(e)))
|
||||||
|
app_obj.progress.emit(0)
|
||||||
|
self.log.error(str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Further parsing
|
||||||
|
app_obj.progress.emit(70)
|
||||||
|
|
||||||
|
filename = args['filename']
|
||||||
|
|
||||||
|
if 'outname' in args:
|
||||||
|
outname = args['outname']
|
||||||
|
else:
|
||||||
|
outname = filename.split('/')[-1].split('\\')[-1]
|
||||||
|
|
||||||
|
follow = None
|
||||||
|
if 'follow' in args:
|
||||||
|
follow = args['follow']
|
||||||
|
|
||||||
|
with self.app.proc_container.new("Opening Gerber"):
|
||||||
|
|
||||||
|
# Object creation
|
||||||
|
self.app.new_object("gerber", outname, obj_init)
|
||||||
|
|
||||||
|
# Register recent file
|
||||||
|
self.app.file_opened.emit("gerber", filename)
|
||||||
|
|
||||||
|
self.app.progress.emit(100)
|
||||||
|
|
||||||
|
# GUI feedback
|
||||||
|
self.app.inform.emit("Opened: " + filename)
|
52
tclCommands/__init__.py
Normal file
52
tclCommands/__init__.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import pkgutil
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 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
|
||||||
|
import tclCommands.TclCommandIsolate
|
||||||
|
import tclCommands.TclCommandNew
|
||||||
|
import tclCommands.TclCommandOpenGerber
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = []
|
||||||
|
|
||||||
|
for loader, name, is_pkg in pkgutil.walk_packages(__path__):
|
||||||
|
module = loader.find_module(name).load_module(name)
|
||||||
|
__all__.append(name)
|
||||||
|
|
||||||
|
def register_all_commands(app, commands):
|
||||||
|
"""
|
||||||
|
Static method which register all known commands.
|
||||||
|
|
||||||
|
Command should be for now in directory tclCommands and module should start with TCLCommand
|
||||||
|
Class have to follow same name as module.
|
||||||
|
|
||||||
|
we need import all modules in top section:
|
||||||
|
import tclCommands.TclCommandExteriors
|
||||||
|
at this stage we can include only wanted commands with this, 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
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
tcl_modules = {k: v for k, v in sys.modules.items() if k.startswith('tclCommands.TclCommand')}
|
||||||
|
|
||||||
|
for key, mod in tcl_modules.items():
|
||||||
|
if key != 'tclCommands.TclCommand':
|
||||||
|
class_name = key.split('.')[1]
|
||||||
|
class_type = getattr(mod, class_name)
|
||||||
|
command_instance = class_type(app)
|
||||||
|
|
||||||
|
for alias in command_instance.aliases:
|
||||||
|
commands[alias] = {
|
||||||
|
'fcn': command_instance.execute_wrapper,
|
||||||
|
'help': command_instance.get_decorated_help()
|
||||||
|
}
|
@ -4,8 +4,7 @@ Shows intput and output text. Allows to enter commands. Supports history.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import cgi
|
import cgi
|
||||||
|
from PyQt4.QtCore import pyqtSignal, Qt
|
||||||
from PyQt4.QtCore import pyqtSignal
|
|
||||||
from PyQt4.QtGui import QColor, QKeySequence, QLineEdit, QPalette, \
|
from PyQt4.QtGui import QColor, QKeySequence, QLineEdit, QPalette, \
|
||||||
QSizePolicy, QTextCursor, QTextEdit, \
|
QSizePolicy, QTextCursor, QTextEdit, \
|
||||||
QVBoxLayout, QWidget
|
QVBoxLayout, QWidget
|
||||||
@ -83,7 +82,6 @@ class _ExpandableTextEdit(QTextEdit):
|
|||||||
# Paste only plain text.
|
# Paste only plain text.
|
||||||
self.insertPlainText(mime_data.text())
|
self.insertPlainText(mime_data.text())
|
||||||
|
|
||||||
|
|
||||||
class TermWidget(QWidget):
|
class TermWidget(QWidget):
|
||||||
"""
|
"""
|
||||||
Widget wich represents terminal. It only displays text and allows to enter text.
|
Widget wich represents terminal. It only displays text and allows to enter text.
|
||||||
@ -118,6 +116,34 @@ class TermWidget(QWidget):
|
|||||||
|
|
||||||
self._edit.setFocus()
|
self._edit.setFocus()
|
||||||
|
|
||||||
|
def open_proccessing(self, detail=None):
|
||||||
|
"""
|
||||||
|
Open processing and disable using shell commands again until all commands are finished
|
||||||
|
|
||||||
|
:param detail: text detail about what is currently called from TCL to python
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._edit.setTextColor(Qt.white)
|
||||||
|
self._edit.setTextBackgroundColor(Qt.darkGreen)
|
||||||
|
if detail is None:
|
||||||
|
self._edit.setPlainText("...proccessing...")
|
||||||
|
else:
|
||||||
|
self._edit.setPlainText("...proccessing... [%s]" % detail)
|
||||||
|
|
||||||
|
self._edit.setDisabled(True)
|
||||||
|
|
||||||
|
def close_proccessing(self):
|
||||||
|
"""
|
||||||
|
Close processing and enable using shell commands again
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._edit.setTextColor(Qt.black)
|
||||||
|
self._edit.setTextBackgroundColor(Qt.white)
|
||||||
|
self._edit.setPlainText('')
|
||||||
|
self._edit.setDisabled(False)
|
||||||
|
|
||||||
def _append_to_browser(self, style, text):
|
def _append_to_browser(self, style, text):
|
||||||
"""
|
"""
|
||||||
Convert text to HTML for inserting it to browser
|
Convert text to HTML for inserting it to browser
|
||||||
@ -225,4 +251,3 @@ class TermWidget(QWidget):
|
|||||||
self._historyIndex -= 1
|
self._historyIndex -= 1
|
||||||
self._edit.setPlainText(self._history[self._historyIndex])
|
self._edit.setPlainText(self._history[self._historyIndex])
|
||||||
self._edit.moveCursor(QTextCursor.End)
|
self._edit.moveCursor(QTextCursor.End)
|
||||||
|
|
||||||
|
26
tests/gerber_files/detector_contour.gbr
Normal file
26
tests/gerber_files/detector_contour.gbr
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
G04 MADE WITH FRITZING*
|
||||||
|
G04 WWW.FRITZING.ORG*
|
||||||
|
G04 DOUBLE SIDED*
|
||||||
|
G04 HOLES PLATED*
|
||||||
|
G04 CONTOUR ON CENTER OF CONTOUR VECTOR*
|
||||||
|
%ASAXBY*%
|
||||||
|
%FSLAX23Y23*%
|
||||||
|
%MOIN*%
|
||||||
|
%OFA0B0*%
|
||||||
|
%SFA1.0B1.0*%
|
||||||
|
%ADD10R,1.771650X1.181100*%
|
||||||
|
%ADD11C,0.008000*%
|
||||||
|
%ADD10C,0.008*%
|
||||||
|
%LNCONTOUR*%
|
||||||
|
G90*
|
||||||
|
G70*
|
||||||
|
G54D10*
|
||||||
|
G54D11*
|
||||||
|
X4Y1177D02*
|
||||||
|
X1768Y1177D01*
|
||||||
|
X1768Y4D01*
|
||||||
|
X4Y4D01*
|
||||||
|
X4Y1177D01*
|
||||||
|
D02*
|
||||||
|
G04 End of contour*
|
||||||
|
M02*
|
2146
tests/gerber_files/detector_copper_bottom.gbr
Normal file
2146
tests/gerber_files/detector_copper_bottom.gbr
Normal file
File diff suppressed because it is too large
Load Diff
71
tests/gerber_files/detector_copper_top.gbr
Normal file
71
tests/gerber_files/detector_copper_top.gbr
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
G04 MADE WITH FRITZING*
|
||||||
|
G04 WWW.FRITZING.ORG*
|
||||||
|
G04 DOUBLE SIDED*
|
||||||
|
G04 HOLES PLATED*
|
||||||
|
G04 CONTOUR ON CENTER OF CONTOUR VECTOR*
|
||||||
|
%ASAXBY*%
|
||||||
|
%FSLAX23Y23*%
|
||||||
|
%MOIN*%
|
||||||
|
%OFA0B0*%
|
||||||
|
%SFA1.0B1.0*%
|
||||||
|
%ADD10C,0.075000*%
|
||||||
|
%ADD11C,0.099055*%
|
||||||
|
%ADD12C,0.078740*%
|
||||||
|
%ADD13R,0.075000X0.075000*%
|
||||||
|
%ADD14C,0.024000*%
|
||||||
|
%ADD15C,0.020000*%
|
||||||
|
%LNCOPPER1*%
|
||||||
|
G90*
|
||||||
|
G70*
|
||||||
|
G54D10*
|
||||||
|
X1149Y872D03*
|
||||||
|
X1349Y872D03*
|
||||||
|
X749Y722D03*
|
||||||
|
X749Y522D03*
|
||||||
|
X1149Y522D03*
|
||||||
|
X1449Y522D03*
|
||||||
|
X1149Y422D03*
|
||||||
|
X1449Y422D03*
|
||||||
|
X1149Y322D03*
|
||||||
|
X1449Y322D03*
|
||||||
|
X1149Y222D03*
|
||||||
|
X1449Y222D03*
|
||||||
|
X949Y472D03*
|
||||||
|
X949Y72D03*
|
||||||
|
G54D11*
|
||||||
|
X749Y972D03*
|
||||||
|
X599Y972D03*
|
||||||
|
X349Y322D03*
|
||||||
|
X349Y472D03*
|
||||||
|
X349Y672D03*
|
||||||
|
X349Y822D03*
|
||||||
|
G54D10*
|
||||||
|
X699Y122D03*
|
||||||
|
X699Y322D03*
|
||||||
|
G54D12*
|
||||||
|
X699Y222D03*
|
||||||
|
X949Y972D03*
|
||||||
|
X749Y622D03*
|
||||||
|
X1049Y222D03*
|
||||||
|
X1249Y872D03*
|
||||||
|
G54D13*
|
||||||
|
X1149Y872D03*
|
||||||
|
X1149Y522D03*
|
||||||
|
G54D14*
|
||||||
|
X952Y946D02*
|
||||||
|
X1045Y249D01*
|
||||||
|
G54D15*
|
||||||
|
X776Y695D02*
|
||||||
|
X721Y695D01*
|
||||||
|
X721Y750D01*
|
||||||
|
X776Y750D01*
|
||||||
|
X776Y695D01*
|
||||||
|
D02*
|
||||||
|
X671Y150D02*
|
||||||
|
X726Y150D01*
|
||||||
|
X726Y95D01*
|
||||||
|
X671Y95D01*
|
||||||
|
X671Y150D01*
|
||||||
|
D02*
|
||||||
|
G04 End of Copper1*
|
||||||
|
M02*
|
46
tests/gerber_files/detector_drill.txt
Normal file
46
tests/gerber_files/detector_drill.txt
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
; NON-PLATED HOLES START AT T1
|
||||||
|
; THROUGH (PLATED) HOLES START AT T100
|
||||||
|
M48
|
||||||
|
INCH
|
||||||
|
T1C0.125984
|
||||||
|
T100C0.031496
|
||||||
|
T101C0.035000
|
||||||
|
T102C0.059055
|
||||||
|
%
|
||||||
|
T1
|
||||||
|
X001488Y010223
|
||||||
|
X001488Y001223
|
||||||
|
X016488Y001223
|
||||||
|
X016488Y010223
|
||||||
|
T100
|
||||||
|
X009488Y009723
|
||||||
|
X007488Y006223
|
||||||
|
X012488Y008723
|
||||||
|
X010488Y002223
|
||||||
|
X006988Y002223
|
||||||
|
T101
|
||||||
|
X014488Y004223
|
||||||
|
X006988Y003223
|
||||||
|
X013488Y008723
|
||||||
|
X011488Y008723
|
||||||
|
X007488Y005223
|
||||||
|
X014488Y003223
|
||||||
|
X014488Y002223
|
||||||
|
X011488Y005223
|
||||||
|
X009488Y000723
|
||||||
|
X011488Y004223
|
||||||
|
X006988Y001223
|
||||||
|
X009488Y004723
|
||||||
|
X007488Y007223
|
||||||
|
X011488Y003223
|
||||||
|
X014488Y005223
|
||||||
|
X011488Y002223
|
||||||
|
T102
|
||||||
|
X003488Y008223
|
||||||
|
X003488Y004723
|
||||||
|
X007488Y009723
|
||||||
|
X003488Y006723
|
||||||
|
X005988Y009723
|
||||||
|
X003488Y003223
|
||||||
|
T00
|
||||||
|
M30
|
180
tests/test_tcl_shell.py
Normal file
180
tests/test_tcl_shell.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from PyQt4 import QtGui
|
||||||
|
from PyQt4.QtCore import QThread
|
||||||
|
|
||||||
|
from FlatCAMApp import App
|
||||||
|
from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMCNCjob, FlatCAMExcellon
|
||||||
|
from ObjectUI import GerberObjectUI, GeometryObjectUI
|
||||||
|
from time import sleep
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
class TclShellTest(unittest.TestCase):
|
||||||
|
|
||||||
|
gerber_files = 'tests/gerber_files'
|
||||||
|
copper_bottom_filename = 'detector_copper_bottom.gbr'
|
||||||
|
copper_top_filename = 'detector_copper_top.gbr'
|
||||||
|
cutout_filename = 'detector_contour.gbr'
|
||||||
|
excellon_filename = 'detector_drill.txt'
|
||||||
|
excellon_name = "excellon"
|
||||||
|
gerber_top_name = "top"
|
||||||
|
gerber_bottom_name = "bottom"
|
||||||
|
gerber_cutout_name = "cutout"
|
||||||
|
engraver_diameter = 0.3
|
||||||
|
cutout_diameter = 3
|
||||||
|
drill_diameter = 0.8
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(self):
|
||||||
|
|
||||||
|
self.setup=True
|
||||||
|
self.app = QtGui.QApplication(sys.argv)
|
||||||
|
# Create App, keep app defaults (do not load
|
||||||
|
# user-defined defaults).
|
||||||
|
self.fc = App(user_defaults=False)
|
||||||
|
self.fc.ui.shell_dock.show()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(self):
|
||||||
|
self.fc.tcl=None
|
||||||
|
self.app.closeAllWindows()
|
||||||
|
del self.fc
|
||||||
|
del self.app
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_set_get_units(self):
|
||||||
|
|
||||||
|
self.fc.exec_command_test('set_sys units MM')
|
||||||
|
self.fc.exec_command_test('new')
|
||||||
|
|
||||||
|
self.fc.exec_command_test('set_sys units IN')
|
||||||
|
self.fc.exec_command_test('new')
|
||||||
|
units=self.fc.exec_command_test('get_sys units')
|
||||||
|
self.assertEquals(units, "IN")
|
||||||
|
|
||||||
|
self.fc.exec_command_test('set_sys units MM')
|
||||||
|
self.fc.exec_command_test('new')
|
||||||
|
units=self.fc.exec_command_test('get_sys units')
|
||||||
|
self.assertEquals(units, "MM")
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerber_flow(self):
|
||||||
|
|
||||||
|
# open gerber files top, bottom and cutout
|
||||||
|
|
||||||
|
|
||||||
|
self.fc.exec_command_test('set_sys units MM')
|
||||||
|
self.fc.exec_command_test('new')
|
||||||
|
|
||||||
|
self.fc.exec_command_test('open_gerber %s/%s -outname %s' % (self.gerber_files, self.copper_top_filename, self.gerber_top_name))
|
||||||
|
gerber_top_obj = self.fc.collection.get_by_name(self.gerber_top_name)
|
||||||
|
self.assertTrue(isinstance(gerber_top_obj, FlatCAMGerber),
|
||||||
|
"Expected FlatCAMGerber, instead, %s is %s" %
|
||||||
|
(self.gerber_top_name, type(gerber_top_obj)))
|
||||||
|
|
||||||
|
self.fc.exec_command_test('open_gerber %s/%s -outname %s' % (self.gerber_files, self.copper_bottom_filename, self.gerber_bottom_name))
|
||||||
|
gerber_bottom_obj = self.fc.collection.get_by_name(self.gerber_bottom_name)
|
||||||
|
self.assertTrue(isinstance(gerber_bottom_obj, FlatCAMGerber),
|
||||||
|
"Expected FlatCAMGerber, instead, %s is %s" %
|
||||||
|
(self.gerber_bottom_name, type(gerber_bottom_obj)))
|
||||||
|
|
||||||
|
self.fc.exec_command_test('open_gerber %s/%s -outname %s' % (self.gerber_files, self.cutout_filename, self.gerber_cutout_name))
|
||||||
|
gerber_cutout_obj = self.fc.collection.get_by_name(self.gerber_cutout_name)
|
||||||
|
self.assertTrue(isinstance(gerber_cutout_obj, FlatCAMGerber),
|
||||||
|
"Expected FlatCAMGerber, instead, %s is %s" %
|
||||||
|
(self.gerber_cutout_name, type(gerber_cutout_obj)))
|
||||||
|
|
||||||
|
# exteriors delete and join geometries for top layer
|
||||||
|
self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_cutout_name, self.engraver_diameter))
|
||||||
|
self.fc.exec_command_test('exteriors %s -outname %s' % (self.gerber_cutout_name + '_iso', self.gerber_cutout_name + '_iso_exterior'))
|
||||||
|
self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_iso'))
|
||||||
|
obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_iso_exterior')
|
||||||
|
self.assertTrue(isinstance(obj, FlatCAMGeometry),
|
||||||
|
"Expected FlatCAMGeometry, instead, %s is %s" %
|
||||||
|
(self.gerber_cutout_name + '_iso_exterior', type(obj)))
|
||||||
|
|
||||||
|
# mirror bottom gerbers
|
||||||
|
self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.gerber_bottom_name, self.gerber_cutout_name))
|
||||||
|
self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.gerber_cutout_name, self.gerber_cutout_name))
|
||||||
|
|
||||||
|
# exteriors delete and join geometries for bottom layer
|
||||||
|
self.fc.exec_command_test('isolate %s -dia %f -outname %s' % (self.gerber_cutout_name, self.engraver_diameter, self.gerber_cutout_name + '_bottom_iso'))
|
||||||
|
self.fc.exec_command_test('exteriors %s -outname %s' % (self.gerber_cutout_name + '_bottom_iso', self.gerber_cutout_name + '_bottom_iso_exterior'))
|
||||||
|
self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_bottom_iso'))
|
||||||
|
obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_bottom_iso_exterior')
|
||||||
|
self.assertTrue(isinstance(obj, FlatCAMGeometry),
|
||||||
|
"Expected FlatCAMGeometry, instead, %s is %s" %
|
||||||
|
(self.gerber_cutout_name + '_bottom_iso_exterior', type(obj)))
|
||||||
|
|
||||||
|
# at this stage we should have 5 objects
|
||||||
|
names = self.fc.collection.get_names()
|
||||||
|
self.assertEqual(len(names), 5,
|
||||||
|
"Expected 5 objects, found %d" % len(names))
|
||||||
|
|
||||||
|
# isolate traces
|
||||||
|
self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_top_name, self.engraver_diameter))
|
||||||
|
self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_bottom_name, self.engraver_diameter))
|
||||||
|
|
||||||
|
# join isolated geometries for top and bottom
|
||||||
|
self.fc.exec_command_test('join_geometries %s %s %s' % (self.gerber_top_name + '_join_iso', self.gerber_top_name + '_iso', self.gerber_cutout_name + '_iso_exterior'))
|
||||||
|
self.fc.exec_command_test('join_geometries %s %s %s' % (self.gerber_bottom_name + '_join_iso', self.gerber_bottom_name + '_iso', self.gerber_cutout_name + '_bottom_iso_exterior'))
|
||||||
|
|
||||||
|
# at this stage we should have 9 objects
|
||||||
|
names = self.fc.collection.get_names()
|
||||||
|
self.assertEqual(len(names), 9,
|
||||||
|
"Expected 9 objects, found %d" % len(names))
|
||||||
|
|
||||||
|
# clean unused isolations
|
||||||
|
self.fc.exec_command_test('delete %s' % (self.gerber_bottom_name + '_iso'))
|
||||||
|
self.fc.exec_command_test('delete %s' % (self.gerber_top_name + '_iso'))
|
||||||
|
self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_iso_exterior'))
|
||||||
|
self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_bottom_iso_exterior'))
|
||||||
|
|
||||||
|
# at this stage we should have 5 objects again
|
||||||
|
names = self.fc.collection.get_names()
|
||||||
|
self.assertEqual(len(names), 5,
|
||||||
|
"Expected 5 objects, found %d" % len(names))
|
||||||
|
|
||||||
|
# geocutout bottom test (it cuts to same object)
|
||||||
|
self.fc.exec_command_test('isolate %s -dia %f -outname %s' % (self.gerber_cutout_name, self.cutout_diameter, self.gerber_cutout_name + '_bottom_iso'))
|
||||||
|
self.fc.exec_command_test('exteriors %s -outname %s' % (self.gerber_cutout_name + '_bottom_iso', self.gerber_cutout_name + '_bottom_iso_exterior'))
|
||||||
|
self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_bottom_iso'))
|
||||||
|
obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_bottom_iso_exterior')
|
||||||
|
self.assertTrue(isinstance(obj, FlatCAMGeometry),
|
||||||
|
"Expected FlatCAMGeometry, instead, %s is %s" %
|
||||||
|
(self.gerber_cutout_name + '_bottom_iso_exterior', type(obj)))
|
||||||
|
self.fc.exec_command_test('geocutout %s -dia %f -gapsize 0.3 -gaps 4' % (self.gerber_cutout_name + '_bottom_iso_exterior', self.cutout_diameter))
|
||||||
|
|
||||||
|
# at this stage we should have 6 objects
|
||||||
|
names = self.fc.collection.get_names()
|
||||||
|
self.assertEqual(len(names), 6,
|
||||||
|
"Expected 6 objects, found %d" % len(names))
|
||||||
|
|
||||||
|
# TODO: tests for tcl
|
||||||
|
|
||||||
|
def test_open_gerber(self):
|
||||||
|
|
||||||
|
self.fc.exec_command_test('set_sys units MM')
|
||||||
|
self.fc.exec_command_test('new')
|
||||||
|
|
||||||
|
self.fc.exec_command_test('open_gerber %s/%s -outname %s' % (self.gerber_files, self.copper_top_filename, self.gerber_top_name))
|
||||||
|
gerber_top_obj = self.fc.collection.get_by_name(self.gerber_top_name)
|
||||||
|
self.assertTrue(isinstance(gerber_top_obj, FlatCAMGerber),
|
||||||
|
"Expected FlatCAMGerber, instead, %s is %s" %
|
||||||
|
(self.gerber_top_name, type(gerber_top_obj)))
|
||||||
|
|
||||||
|
def test_excellon_flow(self):
|
||||||
|
|
||||||
|
self.fc.exec_command_test('set_sys units MM')
|
||||||
|
self.fc.exec_command_test('new')
|
||||||
|
self.fc.exec_command_test('open_excellon %s/%s -outname %s' % (self.gerber_files, self.excellon_filename, self.excellon_name))
|
||||||
|
excellon_obj = self.fc.collection.get_by_name(self.excellon_name)
|
||||||
|
self.assertTrue(isinstance(excellon_obj, FlatCAMExcellon),
|
||||||
|
"Expected FlatCAMExcellon, instead, %s is %s" %
|
||||||
|
(self.excellon_name, type(excellon_obj)))
|
||||||
|
|
||||||
|
# mirror bottom excellon
|
||||||
|
self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.excellon_name, self.gerber_cutout_name))
|
||||||
|
|
||||||
|
# TODO: tests for tcl
|
Loading…
Reference in New Issue
Block a user