This commit is contained in:
Juan Pablo Caram 2016-03-17 17:35:13 -04:00
commit 7b29c52951
2 changed files with 224 additions and 91 deletions

View File

@ -644,6 +644,15 @@ class App(QtCore.QObject):
else:
self.defaults['stats'][resource] = 1
def raiseTclError(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)
def exec_command(self, text):
"""
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
@ -659,7 +668,11 @@ class App(QtCore.QObject):
result = self.tcl.eval(str(text))
self.shell.append_output(result + '\n')
except Tkinter.TclError, e:
self.shell.append_error('ERROR: ' + str(e) + '\n')
#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
"""
@ -2202,7 +2215,7 @@ class App(QtCore.QObject):
def geocutout(name, *args):
"""
subtract gaps from geometry, this will not create new object
Subtract gaps from geometry, this will not create new object
:param name:
:param args:
@ -2213,7 +2226,7 @@ class App(QtCore.QObject):
'gapsize': float,
'gaps': str}
#way gaps wil be rendered:
# How gaps wil be rendered:
# lr - left + right
# tb - top + bottom
# 4 - left + right +top + bottom
@ -2231,24 +2244,53 @@ class App(QtCore.QObject):
except:
return "Could not retrieve object: %s" % name
#get min and max data for each object as we just cut rectangles across X or Y
# Get min and max data for each object as we just cut rectangles across X or Y
xmin, ymin, xmax, ymax = obj.bounds()
px = 0.5 * (xmin + xmax)
py = 0.5 * (ymin + ymax)
lenghtx = (xmax - xmin)
lenghty = (ymax - ymin)
gapsize = kwa['gapsize']+kwa['dia']/2
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)
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)
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)
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)
subtract_rectangle(name,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)
return 'Ok'
def mirror(name, *args):
@ -2274,14 +2316,12 @@ class App(QtCore.QObject):
if not isinstance(obj, FlatCAMGerber) and not isinstance(obj, FlatCAMExcellon):
return "ERROR: Only Gerber and Excellon objects can be mirrored."
# Axis
try:
axis = kwa['axis'].upper()
except KeyError:
return "ERROR: Specify -axis X or -axis Y"
# Box
if 'box' in kwa:
try:
@ -2354,20 +2394,23 @@ class App(QtCore.QObject):
else:
gridoffsety=kwa['gridoffsety']
# Tools
tools = {"1": {"C": kwa['dia']}}
def aligndrillgrid_init_me(init_obj, app_obj):
drills = []
currenty=0
for row in range(kwa['rows']):
currentx=0
for col in range(kwa['columns']):
point = Point(currentx+gridoffsetx,currenty+gridoffsety)
point = Point(currentx + gridoffsetx, currenty + gridoffsety)
drills.append({"point": point, "tool": "1"})
currentx=currentx+kwa['gridx']
currenty=currenty+kwa['gridy']
currentx = currentx + kwa['gridx']
currenty = currenty + kwa['gridy']
init_obj.tools = tools
init_obj.drills = drills
init_obj.create_geometry()
@ -2441,23 +2484,32 @@ class App(QtCore.QObject):
else:
axisoffset=0
#this will align hole to given aligngridoffset and minimal offset from pcb, based on selected axis
# This will align hole to given aligngridoffset and minimal offset from pcb, based on selected axis
if axis == "X":
firstpoint=kwa['gridoffset']
while (xmin-kwa['minoffset'])<firstpoint:
firstpoint=firstpoint-kwa['grid']
lastpoint=kwa['gridoffset']
while (xmax+kwa['minoffset'])>lastpoint:
lastpoint=lastpoint+kwa['grid']
localHoles=(firstpoint,axisoffset),(lastpoint,axisoffset)
firstpoint = kwa['gridoffset']
while (xmin - kwa['minoffset']) < firstpoint:
firstpoint = firstpoint - kwa['grid']
lastpoint = kwa['gridoffset']
while (xmax + kwa['minoffset']) > lastpoint:
lastpoint = lastpoint + kwa['grid']
localHoles = (firstpoint, axisoffset), (lastpoint, axisoffset)
else:
firstpoint=kwa['gridoffset']
while (ymin-kwa['minoffset'])<firstpoint:
firstpoint=firstpoint-kwa['grid']
lastpoint=kwa['gridoffset']
while (ymax+kwa['minoffset'])>lastpoint:
firstpoint = kwa['gridoffset']
while (ymin - kwa['minoffset']) < firstpoint:
firstpoint = firstpoint - kwa['grid']
lastpoint = kwa['gridoffset']
while (ymax + kwa['minoffset']) > lastpoint:
lastpoint=lastpoint+kwa['grid']
localHoles=(axisoffset,firstpoint),(axisoffset,lastpoint)
localHoles = (axisoffset, firstpoint), (axisoffset, lastpoint)
for hole in localHoles:
point = Point(hole)
@ -2506,8 +2558,13 @@ class App(QtCore.QObject):
return 'Ok'
def drillcncjob(name, *args):
def drillcncjob(name=None, *args):
'''
TCL shell command - see help section
:param name: name of object
:param args: array of arguments
:return: "Ok" if completed without errors
'''
a, kwa = h(*args)
types = {'tools': str,
'outname': str,
@ -2518,109 +2575,131 @@ class App(QtCore.QObject):
'toolchange': int
}
if name is None:
self.raiseTclError('Argument name is missing.')
for key in kwa:
if key not in types:
return 'Unknown parameter: %s' % key
kwa[key] = types[key](kwa[key])
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, str(types[key])))
try:
obj = self.collection.get_by_name(str(name))
except:
return "Could not retrieve object: %s" % name
self.raiseTclError("Could not retrieve object: %s" % name)
if obj is None:
return "Object not found: %s" % name
self.raiseTclError('Object not found: %s' % name)
if not isinstance(obj, FlatCAMExcellon):
return "ERROR: Only Excellon objects can be drilled."
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):
assert isinstance(job_obj, FlatCAMCNCjob), \
"Initializer expected FlatCAMCNCjob, got %s" % type(job_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:
return "Operation failed: %s" % str(e)
self.raiseTclError("Operation failed: %s" % str(e))
return 'Ok'
def drillmillgeometry(name, *args):
def millholes(name=None, *args):
'''
TCL shell command - see help section
:param name: name of object
: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.')
for key in kwa:
if key not in types:
return 'Unknown parameter: %s' % key
kwa[key] = types[key](kwa[key])
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]))
try:
if 'tools' in kwa:
kwa['tools'] = [x.strip() for x in kwa['tools'].split(",")]
except Exception as e:
return "Bad tools: %s" % str(e)
self.raiseTclError("Bad tools: %s" % str(e))
try:
obj = self.collection.get_by_name(str(name))
except:
return "Could not retrieve object: %s" % name
self.raiseTclError("Could not retrieve object: %s" % name)
if obj is None:
return "Object not found: %s" % name
self.raiseTclError("Object not found: %s" % name)
assert isinstance(obj, FlatCAMExcellon), \
"Expected a FlatCAMExcellon object, got %s" % 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:
return "Operation failed: %s" % str(e)
self.raiseTclError("Operation failed: %s" % str(e))
if not success:
return msg
self.raiseTclError(msg)
return 'Ok'
def exteriors(obj_name, *args):
def exteriors(name=None, *args):
'''
TCL shell command - see help section
:param name: name of object
: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.')
for key in kwa:
if key not in types:
return 'Unknown parameter: %s' % key
kwa[key] = types[key](kwa[key])
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]))
try:
obj = self.collection.get_by_name(str(obj_name))
obj = self.collection.get_by_name(str(name))
except:
return "Could not retrieve object: %s" % obj_name
self.raiseTclError("Could not retrieve object: %s" % name)
if obj is None:
return "Object not found: %s" % obj_name
self.raiseTclError("Object not found: %s" % name)
assert isinstance(obj, Geometry), \
"Expected a Geometry, got %s" % type(obj)
obj_exteriors = obj.get_exteriors()
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
@ -2628,36 +2707,47 @@ class App(QtCore.QObject):
if 'outname' in kwa:
outname = kwa['outname']
else:
outname = obj_name + ".exteriors"
outname = name + ".exteriors"
try:
obj_exteriors = obj.get_exteriors()
self.new_object('geometry', outname, geo_init)
except Exception as e:
return "Failed: %s" % str(e)
self.raiseTclError("Failed: %s" % str(e))
return 'Ok'
def interiors(obj_name, *args):
def interiors(name=None, *args):
'''
TCL shell command - see help section
:param name: name of object
:param args: array of arguments
:return: "Ok" if completed without errors
'''
a, kwa = h(*args)
types = {}
types = {'outname': str}
for key in kwa:
if key not in types:
return 'Unknown parameter: %s' % key
kwa[key] = types[key](kwa[key])
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 name is None:
self.raiseTclError('Argument name is missing.')
try:
obj = self.collection.get_by_name(str(obj_name))
obj = self.collection.get_by_name(str(name))
except:
return "Could not retrieve object: %s" % obj_name
self.raiseTclError("Could not retrieve object: %s" % name)
if obj is None:
return "Object not found: %s" % obj_name
self.raiseTclError("Object not found: %s" % name)
assert isinstance(obj, Geometry), \
"Expected a Geometry, got %s" % type(obj)
obj_interiors = obj.get_interiors()
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_interiors
@ -2665,16 +2755,23 @@ class App(QtCore.QObject):
if 'outname' in kwa:
outname = kwa['outname']
else:
outname = obj_name + ".interiors"
outname = name + ".interiors"
try:
obj_interiors = obj.get_interiors()
self.new_object('geometry', outname, geo_init)
except Exception as e:
return "Failed: %s" % str(e)
self.raiseTclError("Failed: %s" % str(e))
return 'Ok'
def isolate(name, *args):
def isolate(name=None, *args):
'''
TCL shell command - see help section
:param name: name of object
:param args: array of arguments
:return: "Ok" if completed without errors
'''
a, kwa = h(*args)
types = {'dia': float,
'passes': int,
@ -2684,24 +2781,29 @@ class App(QtCore.QObject):
for key in kwa:
if key not in types:
return 'Unknown parameter: %s' % key
kwa[key] = types[key](kwa[key])
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]))
try:
obj = self.collection.get_by_name(str(name))
except:
return "Could not retrieve object: %s" % name
self.raiseTclError("Could not retrieve object: %s" % name)
if obj is None:
return "Object not found: %s" % name
self.raiseTclError("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)))
try:
obj.isolate(**kwa)
except Exception, e:
return "Operation failed: %s" % str(e)
self.raiseTclError("Operation failed: %s" % str(e))
return 'Ok'
@ -3080,6 +3182,34 @@ class App(QtCore.QObject):
return "ERROR: No such system parameter."
'''
Howto implement TCL shell commands:
All parameters passed to command should be possible to set as None and test it afterwards.
This is because we need to see error caused in tcl,
if None value as default parameter is not allowed TCL will return empty error.
Use:
def mycommand(name=None,...):
Test it like this:
if name is None:
self.raiseTclError('Argument name is missing.')
When error ocurre, always use raiseTclError, 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,
which is catched in exec_command and displayed in TCL shell console with red background.
Error in console is displayed with TCL trace.
This behavior works only within main thread,
errors with promissed tasks can be catched and detected only with log.
TODO: this problem have to be addressed somehow, maybe rewrite promissing to be blocking somehow for TCL shell.
Kamil's comment: I will rewrite existing TCL commands from time to time to follow this rules.
'''
commands = {
'mytest': {
'fcn': mytest,
@ -3261,7 +3391,7 @@ class App(QtCore.QObject):
" toolchange: Enable tool changes (example: 1)\n"
},
'millholes': {
'fcn': drillmillgeometry,
'fcn': millholes,
'help': "Create Geometry Object for milling holes from Excellon.\n" +
"> millholes <name> -tools <str> -tooldia <float> -outname <str> \n" +
" name: Name of the Excellon Object\n" +

View File

@ -6,7 +6,7 @@ from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMCNCjob
from ObjectUI import GerberObjectUI, GeometryObjectUI
from time import sleep
import os
import tempfile
class GerberFlowTestCase(unittest.TestCase):
@ -128,7 +128,10 @@ class GerberFlowTestCase(unittest.TestCase):
# Export G-Code, check output
#-----------------------------------------
assert isinstance(cnc_obj, FlatCAMCNCjob)
output_filename = "tests/tmp/" + cnc_name + ".gcode"
output_filename = ""
# get system temporary file(try create it and delete also)
with tempfile.NamedTemporaryFile(prefix='unittest.', suffix="." + cnc_name + '.gcode', delete=True) as tmp_file:
output_filename = tmp_file.name
cnc_obj.export_gcode(output_filename)
self.assertTrue(os.path.isfile(output_filename))
os.remove(output_filename)