This commit is contained in:
Juan Pablo Caram 2016-04-10 15:48:40 -04:00
commit 3f7e4a5966
26 changed files with 4100 additions and 278 deletions

View File

@ -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_())

View File

@ -1,4 +1,4 @@
import sys
import sys, traceback
import urllib
import getopt
import random
@ -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)
@ -283,6 +286,8 @@ class App(QtCore.QObject):
"cncjob_tooldia": 0.016,
"cncjob_prepend": "",
"cncjob_append": "",
"background_timeout": 300000, #default value is 5 minutes
"verbose_error_level": 0, # shell verbosity 0 = default(python trace only for unknown errors), 1 = show trace(show trace allways), 2 = (For the future).
# Persistence
"last_folder": None,
@ -528,8 +533,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 +564,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 +677,111 @@ 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
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
:param text: text of error
: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):
"""
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 +1115,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 +1128,7 @@ class App(QtCore.QObject):
:return: None
"""
objs = self.collection.get_selected()
def initialize(obj, app):
@ -1483,6 +1576,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 +1840,7 @@ class App(QtCore.QObject):
:param outname:
:return:
"""
self.log.debug("export_svg()")
try:
@ -1770,7 +1867,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 +2506,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 +2869,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 +2934,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 +2987,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 +3038,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 +3098,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 +3511,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 +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
for cmd in commands:
self.tcl.createcommand(cmd, commands[cmd]['fcn'])

View File

@ -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:

View File

@ -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.")

View File

@ -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.
@ -2756,7 +2777,7 @@ class CNCjob(Geometry):
# so we actually are sorting the tools by diameter
sorted_tools = sorted(exobj.tools.items(), key = lambda x: x[1])
if tools == "all":
tools = str([i[0] for i in sorted_tools]) # we get a string of ordered tools
tools = [i[0] for i in sorted_tools] # we get a array of ordered tools
log.debug("Tools 'all' and sorted are: %s" % str(tools))
else:
selected_tools = [x.strip() for x in tools.split(",")] # we strip spaces and also separate the tools by ','
@ -2797,24 +2818,26 @@ class CNCjob(Geometry):
for tool in tools:
# Tool change sequence (optional)
if toolchange:
gcode += "G00 Z%.4f\n" % toolchangez
gcode += "T%d\n" % int(tool) # Indicate tool slot (for automatic tool changer)
gcode += "M5\n" # Spindle Stop
gcode += "M6\n" # Tool change
gcode += "(MSG, Change to tool dia=%.4f)\n" % exobj.tools[tool]["C"]
gcode += "M0\n" # Temporary machine stop
if self.spindlespeed is not None:
gcode += "M03 S%d\n" % int(self.spindlespeed) # Spindle start with configured speed
else:
gcode += "M03\n" # Spindle start
# only if tool have some points, otherwise thre may be error and this part is useless
if tool in points:
# Tool change sequence (optional)
if toolchange:
gcode += "G00 Z%.4f\n" % toolchangez
gcode += "T%d\n" % int(tool) # Indicate tool slot (for automatic tool changer)
gcode += "M5\n" # Spindle Stop
gcode += "M6\n" # Tool change
gcode += "(MSG, Change to tool dia=%.4f)\n" % exobj.tools[tool]["C"]
gcode += "M0\n" # Temporary machine stop
if self.spindlespeed is not None:
gcode += "M03 S%d\n" % int(self.spindlespeed) # Spindle start with configured speed
else:
gcode += "M03\n" # Spindle start
# Drillling!
for point in points[tool]:
x, y = point.coords.xy
gcode += t % (x[0], y[0])
gcode += down + up
# Drillling!
for point in points[tool]:
x, y = point.coords.xy
gcode += t % (x[0], y[0])
gcode += down + up
gcode += t % (0, 0)
gcode += "M05\n" # Spindle stop

Binary file not shown.

Binary file not shown.

Binary file not shown.

394
tclCommands/TclCommand.py Normal file
View 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)

View 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()

View 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()

View 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)

View 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)

View 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)

View 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)

View 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)

View 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)

View 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()

View 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
View 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()
}

View File

@ -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)

View 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*

File diff suppressed because it is too large Load Diff

View 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*

View 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
View 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