Merged in sopak/flatcam/tcl-commands (pull request #37)
TCL commands redesign
This commit is contained in:
commit
d730335fed
@ -1,5 +1,6 @@
|
||||
import sys
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from FlatCAMApp import App
|
||||
|
||||
def debug_trace():
|
||||
@ -10,6 +11,10 @@ def debug_trace():
|
||||
#set_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)
|
||||
fc = App()
|
||||
sys.exit(app.exec_())
|
562
FlatCAMApp.py
562
FlatCAMApp.py
@ -27,7 +27,7 @@ from FlatCAMDraw import FlatCAMDraw
|
||||
from FlatCAMProcess import *
|
||||
from MeasurementTool import Measurement
|
||||
from DblSidedTool import DblSidedTool
|
||||
|
||||
import tclCommands
|
||||
|
||||
########################################
|
||||
## App ##
|
||||
@ -107,6 +107,9 @@ class App(QtCore.QObject):
|
||||
|
||||
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
|
||||
# in the worker task.
|
||||
thread_exception = QtCore.pyqtSignal(object)
|
||||
@ -528,8 +531,8 @@ class App(QtCore.QObject):
|
||||
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("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.setWidget(self.shell)
|
||||
@ -559,6 +562,17 @@ class App(QtCore.QObject):
|
||||
|
||||
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):
|
||||
for option in self.defaults_form_fields:
|
||||
self.defaults[option] = self.defaults_form_fields[option].get_value()
|
||||
@ -661,36 +675,77 @@ class App(QtCore.QObject):
|
||||
else:
|
||||
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
|
||||
: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
|
||||
"""
|
||||
|
||||
self.tcl.eval('return -code error "%s"' % text)
|
||||
raise Exception(text)
|
||||
raise self.TclErrorException(text)
|
||||
|
||||
def exec_command(self, text):
|
||||
"""
|
||||
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
|
||||
: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)
|
||||
|
||||
try:
|
||||
self.shell.open_proccessing()
|
||||
result = self.tcl.eval(str(text))
|
||||
self.shell.append_output(result + '\n')
|
||||
if result!='None':
|
||||
self.shell.append_output(result + '\n')
|
||||
except Tkinter.TclError, e:
|
||||
#this will display more precise answer if something in TCL shell fail
|
||||
result = self.tcl.eval("set errorInfo")
|
||||
self.log.error("Exec command Exception: %s" % (result + '\n'))
|
||||
self.shell.append_error('ERROR: ' + result + '\n')
|
||||
#show error in console and just return
|
||||
return
|
||||
#show error in console and just return or in test raise exception
|
||||
if reraise:
|
||||
raise e
|
||||
finally:
|
||||
self.shell.close_proccessing()
|
||||
pass
|
||||
return result
|
||||
|
||||
"""
|
||||
Code below is unsused. Saved for later.
|
||||
@ -1024,6 +1079,7 @@ class App(QtCore.QObject):
|
||||
toggle shell if is visible close it if closed open it
|
||||
:return:
|
||||
"""
|
||||
|
||||
if self.ui.shell_dock.isVisible():
|
||||
self.ui.shell_dock.hide()
|
||||
else:
|
||||
@ -1036,6 +1092,7 @@ class App(QtCore.QObject):
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
objs = self.collection.get_selected()
|
||||
|
||||
def initialize(obj, app):
|
||||
@ -1483,6 +1540,9 @@ class App(QtCore.QObject):
|
||||
|
||||
self.plotcanvas.clear()
|
||||
|
||||
# tcl needs to be reinitialized, otherwise old shell variables etc remains
|
||||
self.init_tcl()
|
||||
|
||||
self.collection.delete_all()
|
||||
|
||||
self.setup_component_editor()
|
||||
@ -1744,6 +1804,7 @@ class App(QtCore.QObject):
|
||||
:param outname:
|
||||
:return:
|
||||
"""
|
||||
|
||||
self.log.debug("export_svg()")
|
||||
|
||||
try:
|
||||
@ -1770,7 +1831,7 @@ class App(QtCore.QObject):
|
||||
svg_header = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
|
||||
svg_header += 'width="' + svgwidth + uom + '" '
|
||||
svg_header += 'height="' + svgheight + uom + '" '
|
||||
svg_header += 'viewBox="' + minx + ' ' + miny + ' ' + svgwidth + ' ' + svgheight + '">'
|
||||
svg_header += 'viewBox="' + minx + ' ' + miny + ' ' + svgwidth + ' ' + svgheight + '">'
|
||||
svg_header += '<g transform="scale(1,-1)">'
|
||||
svg_footer = '</g> </svg>'
|
||||
svg_elem = svg_header + exported_svg + svg_footer
|
||||
@ -2409,85 +2470,96 @@ class App(QtCore.QObject):
|
||||
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
|
||||
|
||||
:param name:
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
a, kwa = h(*args)
|
||||
types = {'dia': float,
|
||||
'gapsize': float,
|
||||
'gaps': str}
|
||||
|
||||
# How gaps wil be rendered:
|
||||
# lr - left + right
|
||||
# tb - top + bottom
|
||||
# 4 - left + right +top + bottom
|
||||
# 2lr - 2*left + 2*right
|
||||
# 2tb - 2*top + 2*bottom
|
||||
# 8 - 2*left + 2*right +2*top + 2*bottom
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
return 'Unknown parameter: %s' % key
|
||||
kwa[key] = types[key](kwa[key])
|
||||
:param name: name of object
|
||||
:param args: array of arguments
|
||||
:return: "Ok" if completed without errors
|
||||
'''
|
||||
|
||||
try:
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
return "Could not retrieve object: %s" % name
|
||||
a, kwa = h(*args)
|
||||
types = {'dia': float,
|
||||
'gapsize': float,
|
||||
'gaps': str}
|
||||
|
||||
# Get min and max data for each object as we just cut rectangles across X or Y
|
||||
xmin, ymin, xmax, ymax = obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
lenghtx = (xmax - xmin)
|
||||
lenghty = (ymax - ymin)
|
||||
gapsize = kwa['gapsize'] + kwa['dia'] / 2
|
||||
|
||||
if kwa['gaps'] == '8' or kwa['gaps']=='2lr':
|
||||
|
||||
subtract_rectangle(name,
|
||||
xmin - gapsize,
|
||||
py - gapsize + lenghty / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize + lenghty / 4)
|
||||
subtract_rectangle(name,
|
||||
xmin-gapsize,
|
||||
py - gapsize - lenghty / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - lenghty / 4)
|
||||
|
||||
if kwa['gaps'] == '8' or kwa['gaps']=='2tb':
|
||||
subtract_rectangle(name,
|
||||
px - gapsize + lenghtx / 4,
|
||||
ymin-gapsize,
|
||||
px + gapsize + lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
subtract_rectangle(name,
|
||||
px - gapsize - lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
|
||||
if kwa['gaps'] == '4' or kwa['gaps']=='lr':
|
||||
subtract_rectangle(name,
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
|
||||
if kwa['gaps'] == '4' or kwa['gaps']=='tb':
|
||||
subtract_rectangle(name,
|
||||
px - gapsize,
|
||||
ymin - gapsize,
|
||||
px + gapsize,
|
||||
ymax + gapsize)
|
||||
|
||||
return 'Ok'
|
||||
# How gaps wil be rendered:
|
||||
# lr - left + right
|
||||
# tb - top + bottom
|
||||
# 4 - left + right +top + bottom
|
||||
# 2lr - 2*left + 2*right
|
||||
# 2tb - 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:
|
||||
if key not in types:
|
||||
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||
try:
|
||||
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:
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||
|
||||
# Get min and max data for each object as we just cut rectangles across X or Y
|
||||
xmin, ymin, xmax, ymax = obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
lenghtx = (xmax - xmin)
|
||||
lenghty = (ymax - ymin)
|
||||
gapsize = kwa['gapsize'] + kwa['dia'] / 2
|
||||
|
||||
if kwa['gaps'] == '8' or kwa['gaps']=='2lr':
|
||||
|
||||
subtract_rectangle(name,
|
||||
xmin - gapsize,
|
||||
py - gapsize + lenghty / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize + lenghty / 4)
|
||||
subtract_rectangle(name,
|
||||
xmin-gapsize,
|
||||
py - gapsize - lenghty / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - lenghty / 4)
|
||||
|
||||
if kwa['gaps'] == '8' or kwa['gaps']=='2tb':
|
||||
subtract_rectangle(name,
|
||||
px - gapsize + lenghtx / 4,
|
||||
ymin-gapsize,
|
||||
px + gapsize + lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
subtract_rectangle(name,
|
||||
px - gapsize - lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
|
||||
if kwa['gaps'] == '4' or kwa['gaps']=='lr':
|
||||
subtract_rectangle(name,
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
|
||||
if kwa['gaps'] == '4' or kwa['gaps']=='tb':
|
||||
subtract_rectangle(name,
|
||||
px - gapsize,
|
||||
ymin - gapsize,
|
||||
px + gapsize,
|
||||
ymax + gapsize)
|
||||
|
||||
except Exception as unknown:
|
||||
self.raise_tcl_unknown_error(unknown)
|
||||
|
||||
def mirror(name, *args):
|
||||
a, kwa = h(*args)
|
||||
@ -2761,59 +2833,63 @@ class App(QtCore.QObject):
|
||||
:param args: array of arguments
|
||||
:return: "Ok" if completed without errors
|
||||
'''
|
||||
a, kwa = h(*args)
|
||||
types = {'tools': str,
|
||||
'outname': str,
|
||||
'drillz': float,
|
||||
'travelz': float,
|
||||
'feedrate': float,
|
||||
'spindlespeed': int,
|
||||
'toolchange': int
|
||||
}
|
||||
|
||||
if name is None:
|
||||
self.raiseTclError('Argument name is missing.')
|
||||
try:
|
||||
a, kwa = h(*args)
|
||||
types = {'tools': str,
|
||||
'outname': str,
|
||||
'drillz': float,
|
||||
'travelz': float,
|
||||
'feedrate': float,
|
||||
'spindlespeed': int,
|
||||
'toolchange': int
|
||||
}
|
||||
|
||||
if name is None:
|
||||
self.raise_tcl_error('Argument name is missing.')
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||
try:
|
||||
kwa[key] = types[key](kwa[key])
|
||||
except Exception, e:
|
||||
self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
self.raiseTclError('Unknown parameter: %s' % key)
|
||||
try:
|
||||
kwa[key] = types[key](kwa[key])
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||
|
||||
if obj is None:
|
||||
self.raise_tcl_error('Object not found: %s' % name)
|
||||
|
||||
if not isinstance(obj, FlatCAMExcellon):
|
||||
self.raise_tcl_error('Only Excellon objects can be drilled, got %s %s.' % (name, type(obj)))
|
||||
|
||||
try:
|
||||
# Get the tools from the list
|
||||
job_name = kwa["outname"]
|
||||
|
||||
# Object initialization function for app.new_object()
|
||||
def job_init(job_obj, app_obj):
|
||||
job_obj.z_cut = kwa["drillz"]
|
||||
job_obj.z_move = kwa["travelz"]
|
||||
job_obj.feedrate = kwa["feedrate"]
|
||||
job_obj.spindlespeed = kwa["spindlespeed"] if "spindlespeed" in kwa else None
|
||||
toolchange = True if "toolchange" in kwa and kwa["toolchange"] == 1 else False
|
||||
job_obj.generate_from_excellon_by_tool(obj, kwa["tools"], toolchange)
|
||||
job_obj.gcode_parse()
|
||||
job_obj.create_geometry()
|
||||
|
||||
obj.app.new_object("cncjob", job_name, job_init)
|
||||
|
||||
except Exception, e:
|
||||
self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
|
||||
self.raise_tcl_error("Operation failed: %s" % str(e))
|
||||
|
||||
try:
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
||||
except Exception as unknown:
|
||||
self.raise_tcl_unknown_error(unknown)
|
||||
|
||||
if obj is None:
|
||||
self.raiseTclError('Object not found: %s' % name)
|
||||
|
||||
if not isinstance(obj, FlatCAMExcellon):
|
||||
self.raiseTclError('Only Excellon objects can be drilled, got %s %s.' % (name, type(obj)))
|
||||
|
||||
try:
|
||||
# Get the tools from the list
|
||||
job_name = kwa["outname"]
|
||||
|
||||
# Object initialization function for app.new_object()
|
||||
def job_init(job_obj, app_obj):
|
||||
job_obj.z_cut = kwa["drillz"]
|
||||
job_obj.z_move = kwa["travelz"]
|
||||
job_obj.feedrate = kwa["feedrate"]
|
||||
job_obj.spindlespeed = kwa["spindlespeed"] if "spindlespeed" in kwa else None
|
||||
toolchange = True if "toolchange" in kwa and kwa["toolchange"] == 1 else False
|
||||
job_obj.generate_from_excellon_by_tool(obj, kwa["tools"], toolchange)
|
||||
job_obj.gcode_parse()
|
||||
job_obj.create_geometry()
|
||||
|
||||
obj.app.new_object("cncjob", job_name, job_init)
|
||||
|
||||
except Exception, e:
|
||||
self.raiseTclError("Operation failed: %s" % str(e))
|
||||
|
||||
return 'Ok'
|
||||
|
||||
def millholes(name=None, *args):
|
||||
'''
|
||||
@ -2822,48 +2898,51 @@ class App(QtCore.QObject):
|
||||
:param args: array of arguments
|
||||
:return: "Ok" if completed without errors
|
||||
'''
|
||||
a, kwa = h(*args)
|
||||
types = {'tooldia': float,
|
||||
'tools': str,
|
||||
'outname': str}
|
||||
|
||||
if name is None:
|
||||
self.raiseTclError('Argument name is missing.')
|
||||
try:
|
||||
a, kwa = h(*args)
|
||||
types = {'tooldia': float,
|
||||
'tools': str,
|
||||
'outname': str}
|
||||
|
||||
if name is None:
|
||||
self.raise_tcl_error('Argument name is missing.')
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||
try:
|
||||
kwa[key] = types[key](kwa[key])
|
||||
except Exception, e:
|
||||
self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
self.raiseTclError('Unknown parameter: %s' % key)
|
||||
try:
|
||||
kwa[key] = types[key](kwa[key])
|
||||
except Exception, e:
|
||||
self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
|
||||
if 'tools' in kwa:
|
||||
kwa['tools'] = [x.strip() for x in kwa['tools'].split(",")]
|
||||
except Exception as e:
|
||||
self.raise_tcl_error("Bad tools: %s" % str(e))
|
||||
|
||||
try:
|
||||
if 'tools' in kwa:
|
||||
kwa['tools'] = [x.strip() for x in kwa['tools'].split(",")]
|
||||
except Exception as e:
|
||||
self.raiseTclError("Bad tools: %s" % str(e))
|
||||
try:
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||
|
||||
try:
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
||||
if obj is None:
|
||||
self.raise_tcl_error("Object not found: %s" % name)
|
||||
|
||||
if obj is None:
|
||||
self.raiseTclError("Object not found: %s" % name)
|
||||
if not isinstance(obj, FlatCAMExcellon):
|
||||
self.raise_tcl_error('Only Excellon objects can be mill drilled, got %s %s.' % (name, type(obj)))
|
||||
|
||||
if not isinstance(obj, FlatCAMExcellon):
|
||||
self.raiseTclError('Only Excellon objects can be mill drilled, got %s %s.' % (name, type(obj)))
|
||||
try:
|
||||
success, msg = obj.generate_milling(**kwa)
|
||||
except Exception as e:
|
||||
self.raise_tcl_error("Operation failed: %s" % str(e))
|
||||
|
||||
try:
|
||||
success, msg = obj.generate_milling(**kwa)
|
||||
except Exception as e:
|
||||
self.raiseTclError("Operation failed: %s" % str(e))
|
||||
if not success:
|
||||
self.raise_tcl_error(msg)
|
||||
|
||||
if not success:
|
||||
self.raiseTclError(msg)
|
||||
|
||||
return 'Ok'
|
||||
except Exception as unknown:
|
||||
self.raise_tcl_unknown_error(unknown)
|
||||
|
||||
def exteriors(name=None, *args):
|
||||
'''
|
||||
@ -2872,46 +2951,49 @@ class App(QtCore.QObject):
|
||||
:param args: array of arguments
|
||||
:return: "Ok" if completed without errors
|
||||
'''
|
||||
a, kwa = h(*args)
|
||||
types = {'outname': str}
|
||||
|
||||
if name is None:
|
||||
self.raiseTclError('Argument name is missing.')
|
||||
try:
|
||||
a, kwa = h(*args)
|
||||
types = {'outname': str}
|
||||
|
||||
if name is None:
|
||||
self.raise_tcl_error('Argument name is missing.')
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||
try:
|
||||
kwa[key] = types[key](kwa[key])
|
||||
except Exception, e:
|
||||
self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
self.raiseTclError('Unknown parameter: %s' % key)
|
||||
try:
|
||||
kwa[key] = types[key](kwa[key])
|
||||
except Exception, e:
|
||||
self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||
|
||||
try:
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
||||
if obj is None:
|
||||
self.raise_tcl_error("Object not found: %s" % name)
|
||||
|
||||
if obj is None:
|
||||
self.raiseTclError("Object not found: %s" % name)
|
||||
if not isinstance(obj, Geometry):
|
||||
self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
|
||||
|
||||
if not isinstance(obj, Geometry):
|
||||
self.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
|
||||
def geo_init(geo_obj, app_obj):
|
||||
geo_obj.solid_geometry = obj_exteriors
|
||||
|
||||
def geo_init(geo_obj, app_obj):
|
||||
geo_obj.solid_geometry = obj_exteriors
|
||||
if 'outname' in kwa:
|
||||
outname = kwa['outname']
|
||||
else:
|
||||
outname = name + ".exteriors"
|
||||
|
||||
if 'outname' in kwa:
|
||||
outname = kwa['outname']
|
||||
else:
|
||||
outname = name + ".exteriors"
|
||||
try:
|
||||
obj_exteriors = obj.get_exteriors()
|
||||
self.new_object('geometry', outname, geo_init)
|
||||
except Exception as e:
|
||||
self.raise_tcl_error("Failed: %s" % str(e))
|
||||
|
||||
try:
|
||||
obj_exteriors = obj.get_exteriors()
|
||||
self.new_object('geometry', outname, geo_init)
|
||||
except Exception as e:
|
||||
self.raiseTclError("Failed: %s" % str(e))
|
||||
|
||||
return 'Ok'
|
||||
except Exception as unknown:
|
||||
self.raise_tcl_unknown_error(unknown)
|
||||
|
||||
def interiors(name=None, *args):
|
||||
'''
|
||||
@ -2920,46 +3002,49 @@ class App(QtCore.QObject):
|
||||
:param args: array of arguments
|
||||
:return: "Ok" if completed without errors
|
||||
'''
|
||||
a, kwa = h(*args)
|
||||
types = {'outname': str}
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
self.raiseTclError('Unknown parameter: %s' % key)
|
||||
try:
|
||||
a, kwa = h(*args)
|
||||
types = {'outname': str}
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||
try:
|
||||
kwa[key] = types[key](kwa[key])
|
||||
except Exception, e:
|
||||
self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
|
||||
|
||||
if name is None:
|
||||
self.raise_tcl_error('Argument name is missing.')
|
||||
|
||||
try:
|
||||
kwa[key] = types[key](kwa[key])
|
||||
except Exception, e:
|
||||
self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||
|
||||
if name is None:
|
||||
self.raiseTclError('Argument name is missing.')
|
||||
if obj is None:
|
||||
self.raise_tcl_error("Object not found: %s" % name)
|
||||
|
||||
try:
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
||||
if not isinstance(obj, Geometry):
|
||||
self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
|
||||
|
||||
if obj is None:
|
||||
self.raiseTclError("Object not found: %s" % name)
|
||||
def geo_init(geo_obj, app_obj):
|
||||
geo_obj.solid_geometry = obj_interiors
|
||||
|
||||
if not isinstance(obj, Geometry):
|
||||
self.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
|
||||
if 'outname' in kwa:
|
||||
outname = kwa['outname']
|
||||
else:
|
||||
outname = name + ".interiors"
|
||||
|
||||
def geo_init(geo_obj, app_obj):
|
||||
geo_obj.solid_geometry = obj_interiors
|
||||
try:
|
||||
obj_interiors = obj.get_interiors()
|
||||
self.new_object('geometry', outname, geo_init)
|
||||
except Exception as e:
|
||||
self.raise_tcl_error("Failed: %s" % str(e))
|
||||
|
||||
if 'outname' in kwa:
|
||||
outname = kwa['outname']
|
||||
else:
|
||||
outname = name + ".interiors"
|
||||
|
||||
try:
|
||||
obj_interiors = obj.get_interiors()
|
||||
self.new_object('geometry', outname, geo_init)
|
||||
except Exception as e:
|
||||
self.raiseTclError("Failed: %s" % str(e))
|
||||
|
||||
return 'Ok'
|
||||
except Exception as unknown:
|
||||
self.raise_tcl_unknown_error(unknown)
|
||||
|
||||
def isolate(name=None, *args):
|
||||
'''
|
||||
@ -2977,29 +3062,29 @@ class App(QtCore.QObject):
|
||||
|
||||
for key in kwa:
|
||||
if key not in types:
|
||||
self.raiseTclError('Unknown parameter: %s' % key)
|
||||
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||
try:
|
||||
kwa[key] = types[key](kwa[key])
|
||||
except Exception, e:
|
||||
self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
|
||||
self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
|
||||
try:
|
||||
obj = self.collection.get_by_name(str(name))
|
||||
except:
|
||||
self.raiseTclError("Could not retrieve object: %s" % name)
|
||||
self.raise_tcl_error("Could not retrieve object: %s" % name)
|
||||
|
||||
if obj is None:
|
||||
self.raiseTclError("Object not found: %s" % name)
|
||||
self.raise_tcl_error("Object not found: %s" % name)
|
||||
|
||||
assert isinstance(obj, FlatCAMGerber), \
|
||||
"Expected a FlatCAMGerber, got %s" % type(obj)
|
||||
|
||||
if not isinstance(obj, FlatCAMGerber):
|
||||
self.raiseTclError('Expected FlatCAMGerber, got %s %s.' % (name, type(obj)))
|
||||
self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (name, type(obj)))
|
||||
|
||||
try:
|
||||
obj.isolate(**kwa)
|
||||
except Exception, e:
|
||||
self.raiseTclError("Operation failed: %s" % str(e))
|
||||
self.raise_tcl_error("Operation failed: %s" % str(e))
|
||||
|
||||
return 'Ok'
|
||||
|
||||
@ -3390,11 +3475,11 @@ class App(QtCore.QObject):
|
||||
Test it like this:
|
||||
if name is None:
|
||||
|
||||
self.raiseTclError('Argument name is missing.')
|
||||
self.raise_tcl_error('Argument name is missing.')
|
||||
|
||||
When error ocurre, always use raiseTclError, never return "sometext" on error,
|
||||
When error ocurre, always use raise_tcl_error, never return "sometext" on error,
|
||||
otherwise we will miss it and processing will silently continue.
|
||||
Method raiseTclError pass error into TCL interpreter, then raise python exception,
|
||||
Method raise_tcl_error pass error into TCL interpreter, then raise python exception,
|
||||
which is catched in exec_command and displayed in TCL shell console with red background.
|
||||
Error in console is displayed with TCL trace.
|
||||
|
||||
@ -3776,6 +3861,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
|
||||
for cmd in commands:
|
||||
self.tcl.createcommand(cmd, commands[cmd]['fcn'])
|
||||
|
@ -1040,6 +1040,10 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
||||
|
||||
self.app.inform.emit("Saved to: " + filename)
|
||||
|
||||
def get_gcode(self, preamble='', postamble=''):
|
||||
#we need this to beable get_gcode separatelly for shell command export_code
|
||||
return preamble + '\n' + self.gcode + "\n" + postamble
|
||||
|
||||
def on_plot_cb_click(self, *args):
|
||||
if self.muted_ui:
|
||||
return
|
||||
@ -1243,7 +1247,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
outname=None,
|
||||
spindlespeed=None,
|
||||
multidepth=None,
|
||||
depthperpass=None):
|
||||
depthperpass=None,
|
||||
use_thread=True):
|
||||
"""
|
||||
Creates a CNCJob out of this Geometry object. The actual
|
||||
work is done by the target FlatCAMCNCjob object's
|
||||
@ -1304,18 +1309,22 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
|
||||
app_obj.progress.emit(80)
|
||||
|
||||
# To be run in separate thread
|
||||
def job_thread(app_obj):
|
||||
with self.app.proc_container.new("Generating CNC Job."):
|
||||
app_obj.new_object("cncjob", outname, job_init)
|
||||
app_obj.inform.emit("CNCjob created: %s" % outname)
|
||||
app_obj.progress.emit(100)
|
||||
|
||||
# Create a promise with the name
|
||||
self.app.collection.promise(outname)
|
||||
if use_thread:
|
||||
# To be run in separate thread
|
||||
def job_thread(app_obj):
|
||||
with self.app.proc_container.new("Generating CNC Job."):
|
||||
app_obj.new_object("cncjob", outname, job_init)
|
||||
app_obj.inform.emit("CNCjob created: %s" % outname)
|
||||
app_obj.progress.emit(100)
|
||||
|
||||
# Send to worker
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
# Create a promise with the name
|
||||
self.app.collection.promise(outname)
|
||||
|
||||
# Send to worker
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
else:
|
||||
self.app.new_object("cncjob", outname, job_init)
|
||||
|
||||
def on_plot_cb_click(self, *args): # TODO: args not needed
|
||||
if self.muted_ui:
|
||||
|
@ -1,5 +1,4 @@
|
||||
from PyQt4 import QtCore
|
||||
#import FlatCAMApp
|
||||
|
||||
|
||||
class Worker(QtCore.QObject):
|
||||
@ -8,15 +7,34 @@ class Worker(QtCore.QObject):
|
||||
in a single independent thread.
|
||||
"""
|
||||
|
||||
# avoid multiple tests for debug availability
|
||||
pydevd_failed = False
|
||||
|
||||
def __init__(self, app, name=None):
|
||||
super(Worker, self).__init__()
|
||||
self.app = app
|
||||
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):
|
||||
|
||||
self.app.log.debug("Worker Started!")
|
||||
|
||||
self.allow_debug()
|
||||
|
||||
# Tasks are queued in the event listener.
|
||||
self.app.worker_task.connect(self.do_worker_task)
|
||||
|
||||
@ -24,10 +42,10 @@ class Worker(QtCore.QObject):
|
||||
|
||||
self.app.log.debug("Running task: %s" % str(task))
|
||||
|
||||
# 'worker_name' property of task allows to target
|
||||
# specific worker.
|
||||
self.allow_debug()
|
||||
|
||||
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):
|
||||
|
||||
try:
|
||||
task['fcn'](*task['params'])
|
||||
@ -37,5 +55,4 @@ class Worker(QtCore.QObject):
|
||||
|
||||
return
|
||||
|
||||
# FlatCAMApp.App.log.debug("Task ignored.")
|
||||
self.app.log.debug("Task ignored.")
|
||||
self.app.log.debug("Task ignored.")
|
||||
|
21
camlib.py
21
camlib.py
@ -136,6 +136,27 @@ class Geometry(object):
|
||||
log.error("Failed to run union on polygons.")
|
||||
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):
|
||||
"""
|
||||
Subtract polygon from the given object. This only operates on the paths in the original geometry, i.e. it converts polygons into paths.
|
||||
|
BIN
camlib.pyc
BIN
camlib.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
358
tclCommands/TclCommand.py
Normal file
358
tclCommands/TclCommand.py
Normal file
@ -0,0 +1,358 @@
|
||||
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))
|
||||
|
||||
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 is not 'timeout':
|
||||
self.raise_tcl_error('Unknown parameter: %s' % key)
|
||||
try:
|
||||
named_args[key] = self.option_types[key](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 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:
|
||||
self.log.error("TCL command '%s' failed." % str(self))
|
||||
self.app.raise_tcl_unknown_error(unknown)
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute(self, args, unnamed_args):
|
||||
"""
|
||||
Direct execute of command, this method should be implemented in each descendant.
|
||||
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
|
||||
"""
|
||||
|
||||
# default timeout for operation is 10 sec, but it can be much more
|
||||
default_timeout = 10000
|
||||
|
||||
output = None
|
||||
|
||||
def execute_call(self, args, unnamed_args):
|
||||
|
||||
try:
|
||||
self.output = self.execute(args, unnamed_args)
|
||||
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=10000):
|
||||
"""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:
|
||||
self.raise_tcl_error(str(ex[0]))
|
||||
|
||||
if status['timed_out']:
|
||||
self.app.raise_tcl_unknown_error('Operation timed out!')
|
||||
|
||||
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.default_timeout
|
||||
|
||||
# set detail for processing, it will be there until next open or close
|
||||
self.app.shell.open_proccessing(self.get_current_command())
|
||||
|
||||
self.output = None
|
||||
|
||||
def handle_finished(obj):
|
||||
self.app.shell_command_finished.disconnect(handle_finished)
|
||||
# TODO: handle output
|
||||
pass
|
||||
|
||||
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:
|
||||
self.log.error("TCL command '%s' failed." % str(self))
|
||||
self.app.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()
|
81
tclCommands/TclCommandCncjob.py
Normal file
81
tclCommands/TclCommandCncjob.py
Normal file
@ -0,0 +1,81 @@
|
||||
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.'),
|
||||
('timeout', 'Max wait for job timeout before error.')
|
||||
]),
|
||||
'examples': []
|
||||
}
|
||||
|
||||
def execute(self, args, unnamed_args):
|
||||
"""
|
||||
execute current TCL shell command
|
||||
|
||||
:param args: array of known named arguments and options
|
||||
:param unnamed_args: array of other values which were passed into command
|
||||
without -somename and we do not have them in known arg_names
|
||||
:return: None or exception
|
||||
"""
|
||||
|
||||
name = args['name']
|
||||
|
||||
if 'outname' not in args:
|
||||
args['outname'] = name + "_cnc"
|
||||
|
||||
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)
|
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
|
||||
import tclCommands.TclCommandAddPolygon
|
||||
import tclCommands.TclCommandAddPolyline
|
||||
import tclCommands.TclCommandCncjob
|
||||
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
|
||||
|
||||
from PyQt4.QtCore import pyqtSignal
|
||||
from PyQt4.QtCore import pyqtSignal, Qt
|
||||
from PyQt4.QtGui import QColor, QKeySequence, QLineEdit, QPalette, \
|
||||
QSizePolicy, QTextCursor, QTextEdit, \
|
||||
QVBoxLayout, QWidget
|
||||
@ -83,7 +82,6 @@ class _ExpandableTextEdit(QTextEdit):
|
||||
# Paste only plain text.
|
||||
self.insertPlainText(mime_data.text())
|
||||
|
||||
|
||||
class TermWidget(QWidget):
|
||||
"""
|
||||
Widget wich represents terminal. It only displays text and allows to enter text.
|
||||
@ -118,6 +116,34 @@ class TermWidget(QWidget):
|
||||
|
||||
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):
|
||||
"""
|
||||
Convert text to HTML for inserting it to browser
|
||||
@ -225,4 +251,3 @@ class TermWidget(QWidget):
|
||||
self._historyIndex -= 1
|
||||
self._edit.setPlainText(self._history[self._historyIndex])
|
||||
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