From b22409d88c0ed493ddb79f75f5b01f24d2a12cab Mon Sep 17 00:00:00 2001 From: Dave Cowden Date: Tue, 5 Apr 2016 21:04:09 -0400 Subject: [PATCH] added describe_parameter --- cadquery/cqgi.py | 56 +++++++++++++++++++++++++++++++++-------------- doc/cqgi.rst | 18 +++++++++++++++ runtests.py | 2 +- tests/TestCQGI.py | 24 ++++++++++++++++++++ 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/cadquery/cqgi.py b/cadquery/cqgi.py index 37da61f7..01701dbc 100644 --- a/cadquery/cqgi.py +++ b/cadquery/cqgi.py @@ -44,9 +44,11 @@ class CQModel(object): self.ast_tree = ast.parse(script_source, CQSCRIPT) self.script_source = script_source self._find_vars() + # TODO: pick up other scirpt metadata: # describe # pick up validation methods + self._find_descriptions() def _find_vars(self): """ @@ -65,6 +67,9 @@ class CQModel(object): if isinstance(node, ast.Assign): assignment_finder.visit_Assign(node) + def _find_descriptions(self): + description_finder = ParameterDescriptionFinder(self.metadata) + description_finder.visit(self.ast_tree) def validate(self, params): """ @@ -99,6 +104,7 @@ class CQModel(object): env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \ .add_entry("build_object", collector.build_object) \ .add_entry("debug", collector.debug) \ + .add_entry("describe_parameter",collector.describe_parameter) \ .build() c = compile(self.ast_tree, CQSCRIPT, 'exec') @@ -173,6 +179,11 @@ class ScriptMetadata(object): def add_script_parameter(self, p): self.parameters[p.name] = p + def add_parameter_description(self,name,description): + print 'Adding Parameter name=%s, desc=%s' % ( name, description ) + p = self.parameters[name] + p.desc = description + class ParameterType(object): pass @@ -213,19 +224,15 @@ class InputParameter: self.varType = None #: help text describing the variable. Only available if the script used describe_parameter() - self.shortDesc = None - - + self.desc = None #: valid values for the variable. Only available if the script used describe_parameter() self.valid_values = [] - - self.ast_node = None @staticmethod - def create(ast_node, var_name, var_type, default_value, valid_values=None, short_desc=None): + def create(ast_node, var_name, var_type, default_value, valid_values=None, desc=None): if valid_values is None: valid_values = [] @@ -234,10 +241,7 @@ class InputParameter: p.ast_node = ast_node p.default_value = default_value p.name = var_name - if short_desc is None: - p.shortDesc = var_name - else: - p.shortDesc = short_desc + p.desc = desc p.varType = var_type p.valid_values = valid_values return p @@ -296,10 +300,9 @@ class ScriptCallback(object): """ self.debugObjects.append(DebugObject(obj,args)) - def describe_parameter(self,var, valid_values, short_desc): + def describe_parameter(self,var_data ): """ - Not yet implemented: allows a script to document - extra metadata about the parameters + Do Nothing-- we parsed the ast ahead of exection to get what we need. """ pass @@ -394,6 +397,30 @@ class EnvironmentBuilder(object): def build(self): return self.env +class ParameterDescriptionFinder(ast.NodeTransformer): + """ + Visits a parse tree, looking for function calls to describe_parameter(var, description ) + """ + def __init__(self, cq_model): + self.cqModel = cq_model + + def visit_Call(self,node): + """ + Called when we see a function call. Is it describe_parameter? + """ + try: + if node.func.id == 'describe_parameter': + #looks like we have a call to our function. + #first parameter is the variable, + #second is the description + varname = node.args[0].id + desc = node.args[1].s + self.cqModel.add_parameter_description(varname,desc) + + except: + print "Unable to handle function call" + pass + return node class ConstantAssignmentFinder(ast.NodeTransformer): """ @@ -404,9 +431,6 @@ class ConstantAssignmentFinder(ast.NodeTransformer): self.cqModel = cq_model def handle_assignment(self, var_name, value_node): - - - try: if type(value_node) == ast.Num: diff --git a/doc/cqgi.rst b/doc/cqgi.rst index c8be3e3d..310f7d26 100644 --- a/doc/cqgi.rst +++ b/doc/cqgi.rst @@ -70,6 +70,24 @@ run code like this:: The :py:meth:`cadquery.cqgi.parse()` method returns a :py:class:`cadquery.cqgi.CQModel` object. +The `metadata`p property of the object contains a `cadquery.cqgi.ScriptMetaData` object, which can be used to discover the +user parameters available. This is useful if the execution environment would like to present a GUI to allow the user to change the +model parameters. Typically, after collecting new values, the environment will supply them in the build() method. + +This code will return a dictionary of parameter values in the model text SCRIPT:: + + parameters = cqgi.parse(SCRIPT).metadata.parameters + +The dictionary you get back is a map where key is the parameter name, and value is an InputParameter object, +which has a name, type, and default value. + +The type is an object which extends ParameterType-- you can use this to determine what kind of widget to render ( checkbox for boolean, for example ). + +The parameter object also has a description, valid values, minimum, and maximum values, if the user has provided them using the +describe_parameter() method. + + + Calling :py:meth:`cadquery.cqgi.CQModel.build()` returns a :py:class:`cadquery.cqgi.BuildResult` object, ,which includes the script execution time, and a success flag. diff --git a/runtests.py b/runtests.py index ad516ced..ee90ce4c 100644 --- a/runtests.py +++ b/runtests.py @@ -8,11 +8,11 @@ import unittest #on py 2.7.x on win suite = unittest.TestSuite() +suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQGI.TestCQGI)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadObjects.TestCadObjects)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestWorkplanes.TestWorkplanes)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQSelectors.TestCQSelectors)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadQuery.TestCadQuery)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestExporters.TestExporters)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestImporters.TestImporters)) -suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQGI.TestCQGI)) unittest.TextTestRunner().run(suite) diff --git a/tests/TestCQGI.py b/tests/TestCQGI.py index ae8c49de..7cc967fe 100644 --- a/tests/TestCQGI.py +++ b/tests/TestCQGI.py @@ -66,6 +66,30 @@ class TestCQGI(BaseTest): result = model.build({'height': 3.0}) self.assertTrue(result.results[0] == "3.0|3.0|bar|1.0") + def test_describe_parameters(self): + script = textwrap.dedent( + """ + a = 2.0 + describe_parameter(a,'FirstLetter') + """ + ) + model = cqgi.CQModel(script) + a_param = model.metadata.parameters['a'] + self.assertTrue(a_param.default_value == 2.0) + self.assertTrue(a_param.desc == 'FirstLetter') + self.assertTrue(a_param.varType == cqgi.NumberParameterType ) + + def test_describe_parameter_invalid_doesnt_fail_script(self): + script = textwrap.dedent( + """ + a = 2.0 + describe_parameter(a, 2 - 1 ) + """ + ) + model = cqgi.CQModel(script) + a_param = model.metadata.parameters['a'] + self.assertTrue(a_param.name == 'a' ) + def test_build_with_exception(self): badscript = textwrap.dedent( """