beginnings of cqgi
This commit is contained in:
parent
9de84a89ac
commit
c12e663638
282
cadquery/cqgi.py
Normal file
282
cadquery/cqgi.py
Normal file
|
@ -0,0 +1,282 @@
|
|||
"""
|
||||
|
||||
The CadQuery Container Environment.
|
||||
Provides classes and tools for executing CadQuery scripts
|
||||
"""
|
||||
import ast
|
||||
import traceback
|
||||
import re
|
||||
import time
|
||||
|
||||
CQSCRIPT= "<cqscript>"
|
||||
BUILD_OBJECT_COLLECTOR = "__boc__"
|
||||
|
||||
class CQModel(object):
|
||||
"""
|
||||
Object that provides a nice interface to a cq script that
|
||||
is following the cce model
|
||||
"""
|
||||
def __init__(self,scriptSource):
|
||||
self.metadata = ScriptMetadata()
|
||||
self.astTree = ast.parse(scriptSource,CQSCRIPT)
|
||||
|
||||
ConstantAssignmentFinder(self.metadata).visit(self.astTree)
|
||||
|
||||
#TODO: pick up other scirpt metadata:
|
||||
# describe
|
||||
# pick up validation methods
|
||||
|
||||
def validate(self,params):
|
||||
raise NotImplementedError("not yet implemented")
|
||||
|
||||
def build(self,params):
|
||||
"""
|
||||
|
||||
:param params: dictionary of parameter values to build with
|
||||
:return:
|
||||
"""
|
||||
self.setParamValues(params)
|
||||
collector = BuildObjectCollector()
|
||||
env = EnvironmentBuilder().withRealBuiltins() \
|
||||
.addEntry("build_object",collector.build_object).build()
|
||||
|
||||
start = time.clock()
|
||||
result = BuildResult()
|
||||
|
||||
|
||||
c = compile(self.astTree,CQSCRIPT,'exec')
|
||||
exec (c,env)
|
||||
if collector.hasResults():
|
||||
result.setSuccessResult(collector.outputObjects)
|
||||
else:
|
||||
raise ValueError("Script did not call build_object-- no output available.")
|
||||
#except Exception,ex:
|
||||
|
||||
# result.setFailureResult(ex)
|
||||
|
||||
end = time.clock()
|
||||
result.buildTime = end - start
|
||||
return result
|
||||
|
||||
def setParamValues(self,params):
|
||||
modelParameters = self.metadata.parameters
|
||||
|
||||
for k,v in params.iteritems():
|
||||
if not modelParameters.has_key(k):
|
||||
raise ValueError("Cannot set value '%s', it is not a parameter of the model." % k)
|
||||
|
||||
p = modelParameters[k]
|
||||
p.setValue(v)
|
||||
|
||||
|
||||
class BuildResult(object):
|
||||
|
||||
def __init__(self):
|
||||
self.buildTime = None
|
||||
self.results = []
|
||||
self.success = False
|
||||
self.exception = None
|
||||
|
||||
def setFailureResult(self,ex):
|
||||
self.exception = ex
|
||||
self.success = False
|
||||
|
||||
def setSuccessResult(self,results):
|
||||
self.results = results
|
||||
self.success = True
|
||||
|
||||
class ScriptMetadata(object):
|
||||
def __init__(self):
|
||||
self.parameters = {}
|
||||
|
||||
def add_script_parameter(self,p):
|
||||
self.parameters[p.name] = p
|
||||
|
||||
class ParameterType(object): pass
|
||||
class NumberParameterType(ParameterType) : pass
|
||||
class StringParameterType(ParameterType) : pass
|
||||
class BooleanParameterType(ParameterType): pass
|
||||
|
||||
|
||||
class InputParameter:
|
||||
|
||||
def __init__(self):
|
||||
self.name = None
|
||||
self.shortDesc = None
|
||||
self.varType = None
|
||||
self.validValues = []
|
||||
self.defaultValue= None
|
||||
self.astNode = None
|
||||
|
||||
@staticmethod
|
||||
def create(astNode,varname,varType, defaultValue,validValues =[],shortDesc = None):
|
||||
p = InputParameter()
|
||||
p.astNode = astNode
|
||||
p.defaultValue = defaultValue
|
||||
p.name = varname
|
||||
if shortDesc == None:
|
||||
p.shortDesc = varname
|
||||
else:
|
||||
p.shortDesc = shortDesc
|
||||
p.varType = varType
|
||||
p.validValues = validValues
|
||||
return p
|
||||
|
||||
def setValue(self,newValue):
|
||||
#todo: check to make sure newValue can be cast correctly?
|
||||
if self.varType == NumberParameterType:
|
||||
self.astNode.n = newValue
|
||||
elif self.varType == StringParameterType:
|
||||
self.astNode.s = str(newValue)
|
||||
elif self.varType == BooleanParameterType:
|
||||
if ( newValue ):
|
||||
self.astNode.value.id = 'True'
|
||||
else:
|
||||
self.astNode.value.id = 'False'
|
||||
else:
|
||||
raise ValueError("Unknown Type of var: ", str(self.varType))
|
||||
|
||||
def __str__(self):
|
||||
return "InputParmaeter: {name=%s, type=%s, defaultValue=%s" % ( self.name, str(self.varType), str(self.defaultValue) )
|
||||
|
||||
class BuildObjectCollector(object):
|
||||
"""
|
||||
Allows a script to provide output objects
|
||||
"""
|
||||
def __init__(self):
|
||||
self.outputObjects = []
|
||||
|
||||
def build_object(self,shape):
|
||||
self.outputObjects.append(shape)
|
||||
|
||||
def hasResults(self):
|
||||
return len(self.outputObjects) > 0
|
||||
|
||||
class ScriptExecutor(object):
|
||||
"""
|
||||
executes a script in a given environment.
|
||||
"""
|
||||
def __init__(self,environment,astTree):
|
||||
|
||||
try:
|
||||
exec ( astTree ) in environment
|
||||
except Exception,ex:
|
||||
|
||||
#an error here means there was a problem compiling the script
|
||||
#try to figure out what line the error was on
|
||||
traceback.print_exc()
|
||||
formatted_lines = traceback.format_exc().splitlines()
|
||||
lineText = ""
|
||||
for f in formatted_lines:
|
||||
if f.find(CQSCRIPT) > -1:
|
||||
m = re.search("line\\s+(\\d+)",f,re.IGNORECASE )
|
||||
if m and m.group(1):
|
||||
lineText = m.group(1)
|
||||
else:
|
||||
lineText = 0
|
||||
|
||||
sse = ScriptExecutionError()
|
||||
sse.line = int(lineText)
|
||||
sse.message = str(ex)
|
||||
raise sse
|
||||
|
||||
class ScriptExecutionError(Exception):
|
||||
"""
|
||||
Represents a script syntax error.
|
||||
Useful for helping clients pinpoint issues with the script
|
||||
interactively
|
||||
"""
|
||||
def __init__(self,line=None,message=None):
|
||||
if line is None:
|
||||
self.line = 0
|
||||
else:
|
||||
self.line = line
|
||||
|
||||
if message is None:
|
||||
self.message = "Unknown Script Error"
|
||||
else:
|
||||
self.message = message
|
||||
|
||||
def fullMessage(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
return "ScriptError [Line %s]: %s" % ( self.line, self.message)
|
||||
|
||||
class EnvironmentBuilder(object):
|
||||
|
||||
def __init__(self):
|
||||
self.env = {}
|
||||
|
||||
def withRealBuiltins(self):
|
||||
return self.withBuiltins(__builtins__)
|
||||
|
||||
def withBuiltins(self,dict):
|
||||
self.env['__builtins__'] = dict
|
||||
return self
|
||||
|
||||
def addEntry(self,name, value):
|
||||
self.env[name] = value
|
||||
return self
|
||||
|
||||
def build(self):
|
||||
return self.env
|
||||
|
||||
class VariableAssignmentChanger(ast.NodeTransformer):
|
||||
"""
|
||||
|
||||
"""
|
||||
def __init__(self, overrides):
|
||||
self.overrides = overrides
|
||||
|
||||
def modify_node(self, varname, node):
|
||||
if self.overrides.has_key(varname):
|
||||
new_value = self.overrides[varname]
|
||||
if type(node) == ast.Num:
|
||||
node.n = new_value
|
||||
elif type(node) == ast.Str:
|
||||
node.s = new_value
|
||||
else:
|
||||
raise ValueError("Unknown Assignment Type:" + str(type(node)))
|
||||
|
||||
def visit_Assign(self, node):
|
||||
left_side = node.targets[0]
|
||||
if type(node.value) in [ast.Num, ast.Str]:
|
||||
self.modify_node(left_side.id, node.value)
|
||||
elif type(node.value) == ast.Tuple:
|
||||
# we have a multi-value assignment
|
||||
for n, v in zip(left_side.elts, node.value.elts):
|
||||
self.modify_node(n.id, v)
|
||||
return node
|
||||
|
||||
class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
"""
|
||||
Visits a parse tree, and adds script parameters to the cqModel
|
||||
"""
|
||||
|
||||
def __init__(self,cqModel):
|
||||
self.cqModel = cqModel
|
||||
|
||||
def handle_assignment(self,varname, valuenode):
|
||||
if type(valuenode) == ast.Num:
|
||||
self.cqModel.add_script_parameter(InputParameter.create(valuenode,varname,NumberParameterType,valuenode.n))
|
||||
elif type(valuenode) == ast.Str:
|
||||
self.cqModel.add_script_parameter(InputParameter.create(valuenode,varname,StringParameterType,valuenode.s))
|
||||
elif type(valuenode == ast.Name):
|
||||
if valuenode.value.Id == 'True':
|
||||
self.cqModel.add_script_parameter(InputParameter.create(valuenode,varname,BooleanParameterType,True))
|
||||
elif valuenode.value.Id == 'False':
|
||||
self.cqModel.add_script_parameter(InputParameter.create(valuenode,varname,BooleanParameterType,True))
|
||||
|
||||
def visit_Assign(self, node):
|
||||
left_side = node.targets[0]
|
||||
if type(node.value) in [ast.Num, ast.Str, ast.Name]:
|
||||
self.handle_assignment(left_side.id,node.value)
|
||||
elif type(node.value) == ast.Tuple:
|
||||
# we have a multi-value assignment
|
||||
for n, v in zip(left_side.elts, node.value.elts):
|
||||
self.handle_assignment(n.id, v)
|
||||
return node
|
37
tests/TestCQGI.py
Normal file
37
tests/TestCQGI.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
"""
|
||||
Tests CQGI functionality
|
||||
"""
|
||||
|
||||
|
||||
from cadquery import cqgi
|
||||
from tests import BaseTest
|
||||
|
||||
TESTSCRIPT = """
|
||||
height=2.0
|
||||
width=3.0
|
||||
(a,b) = (1.0,1.0)
|
||||
foo="bar"
|
||||
|
||||
result = "%s|%s|%s|%s" % ( str(height) , str(width) , foo , str(a) )
|
||||
build_object(result)
|
||||
"""
|
||||
|
||||
class TestCQGI(BaseTest):
|
||||
|
||||
|
||||
def test_parser(self):
|
||||
model = cqgi.CQModel(TESTSCRIPT)
|
||||
metadata = model.metadata
|
||||
self.assertEquals( len(metadata.parameters) , 5 )
|
||||
|
||||
def test_build_with_empty_params(self):
|
||||
model = cqgi.CQModel(TESTSCRIPT)
|
||||
result = model.build({}) #building with no params should have no affect on the output
|
||||
self.assertTrue(result.success)
|
||||
self.assertTrue(len(result.results) == 1)
|
||||
self.assertTrue(result.results[0] == "2.0|3.0|bar|1.0")
|
||||
|
||||
def test_build_with_different_params(self):
|
||||
model = cqgi.CQModel(TESTSCRIPT)
|
||||
result = model.build({ 'height':3.0})
|
||||
self.assertTrue(result.results[0] == "3.0|3.0|bar|1.0")
|
|
@ -51,4 +51,4 @@ class BaseTest(unittest.TestCase):
|
|||
for i, j in zip(actual, expected):
|
||||
self.assertAlmostEquals(i, j, places)
|
||||
|
||||
__all__ = ['TestCadObjects', 'TestCadQuery', 'TestCQSelectors', 'TestWorkplanes', 'TestExporters', 'TestCQSelectors', 'TestImporters']
|
||||
__all__ = ['TestCadObjects', 'TestCadQuery', 'TestCQSelectors', 'TestWorkplanes', 'TestExporters', 'TestCQSelectors', 'TestImporters','TestCQGI']
|
||||
|
|
Loading…
Reference in New Issue
Block a user