From 190980d4a14d0dbd5ac01a9417b12888e2350f4c Mon Sep 17 00:00:00 2001 From: Dave Cowden Date: Tue, 8 Dec 2015 22:36:19 -0500 Subject: [PATCH] got first directive workign --- cadquery/__init__.py | 4 ++- cadquery/cq_directive.py | 56 +++++++++++++++++------------------ cadquery/cqgi.py | 64 +++++++++++++++++++++++++++++----------- doc/examples.rst | 7 +++-- tests/TestCQGI.py | 37 ++++++++++++++++------- 5 files changed, 110 insertions(+), 58 deletions(-) diff --git a/cadquery/__init__.py b/cadquery/__init__.py index fb0bea0..22a4970 100644 --- a/cadquery/__init__.py +++ b/cadquery/__init__.py @@ -13,7 +13,9 @@ from .CQ import * __all__ = [ 'CQ','Workplane','plugins','selectors','Plane','BoundBox','Matrix','Vector','sortWiresByBuildOrder', - 'Shape','Vertex','Edge','Wire','Solid','Shell','Compound','exporters', 'importers', 'NearestToPointSelector','ParallelDirSelector','DirectionSelector','PerpendicularDirSelector','TypeSelector','DirectionMinMaxSelector','StringSyntaxSelector','Selector','plugins' + 'Shape','Vertex','Edge','Wire','Solid','Shell','Compound','exporters', 'importers', + 'NearestToPointSelector','ParallelDirSelector','DirectionSelector','PerpendicularDirSelector', + 'TypeSelector','DirectionMinMaxSelector','StringSyntaxSelector','Selector','plugins' ] __version__ = "0.3.0" diff --git a/cadquery/cq_directive.py b/cadquery/cq_directive.py index d898d27..958a3dd 100644 --- a/cadquery/cq_directive.py +++ b/cadquery/cq_directive.py @@ -3,19 +3,18 @@ A special directive for including a cq object. """ -import sys, os, shutil, imp, warnings, cStringIO, re,traceback - +import traceback from cadquery import * +from cadquery import cqgi import StringIO from docutils.parsers.rst import directives - template = """ .. raw:: html -
- %(outSVG)s +
+ %(out_svg)s
@@ -25,37 +24,39 @@ template_content_indent = ' ' def cq_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - - #only consider inline snippets + content_offset, block_text, state, state_machine): + # only consider inline snippets plot_code = '\n'.join(content) # Since we don't have a filename, use a hash based on the content - #the script must define a variable called 'out', which is expected to - #be a CQ object - outSVG = "Your Script Did not assign the 'result' variable!" - + # the script must define a variable called 'out', which is expected to + # be a CQ object + out_svg = "Your Script Did not assign call build_output() function!" try: _s = StringIO.StringIO() - exec(plot_code) - - exporters.exportShape(result,"SVG",_s) - outSVG = _s.getvalue() - except: - traceback.print_exc() - outSVG = traceback.format_exc() + result = cqgi.execute(plot_code) - #now out + if result.success: + exporters.exportShape(result.first_result, "SVG", _s) + out_svg = _s.getvalue() + else: + raise result.exception + + except Exception: + traceback.print_exc() + out_svg = traceback.format_exc() + + # now out # Now start generating the lines of output lines = [] - #get rid of new lines - outSVG = outSVG.replace('\n','') + # get rid of new lines + out_svg = out_svg.replace('\n', '') - txtAlign = "left" - if options.has_key("align"): - txtAlign = options['align'] + txt_align = "left" + if "align" in options: + txt_align = options['align'] lines.extend((template % locals()).split('\n')) @@ -70,6 +71,7 @@ def cq_directive(name, arguments, options, content, lineno, return [] + def setup(app): setup.app = app setup.config = app.config @@ -78,8 +80,6 @@ def setup(app): options = {'height': directives.length_or_unitless, 'width': directives.length_or_percentage_or_unitless, 'align': directives.unchanged - } + } app.add_directive('cq_plot', cq_directive, True, (0, 2, 0), **options) - - diff --git a/cadquery/cqgi.py b/cadquery/cqgi.py index fd0b6b6..fda972e 100644 --- a/cadquery/cqgi.py +++ b/cadquery/cqgi.py @@ -7,10 +7,30 @@ import ast import traceback import re import time +import cadquery CQSCRIPT = "" +def execute(script_source, build_parameters=None): + """ + Executes the provided model, using the specified variables. + + If you would prefer to access the underlying model without building it, + for example, to inspect its available parameters, construct a CQModel object. + + :param script_source: the script to run. Must be a valid cadquery script + :param build_parameters: a dictionary of variables. The variables must be + assignable to the underlying variable type. + :raises: Nothing. If there is an exception, it will be on the exception property of the result. + This is the interface so that we can return other information onthe result, such as the build time + :return: a BuildResult object, which includes the status of the result, and either + a resulting shape or an exception + """ + model = CQModel(script_source) + return model.build(build_parameters) + + class CQModel(object): """ Object that provides a nice interface to a cq script that @@ -19,9 +39,9 @@ class CQModel(object): def __init__(self, script_source): self.metadata = ScriptMetadata() - self.astTree = ast.parse(script_source, CQSCRIPT) + self.ast_tree = ast.parse(script_source, CQSCRIPT) - ConstantAssignmentFinder(self.metadata).visit(self.astTree) + ConstantAssignmentFinder(self.metadata).visit(self.ast_tree) # TODO: pick up other scirpt metadata: # describe @@ -39,21 +59,21 @@ class CQModel(object): if not params: params = {} - self.set_param_values(params) - collector = BuildObjectCollector() - env = EnvironmentBuilder().with_real_builtins() \ - .add_entry("build_object", collector.build_object).build() - start = time.clock() result = BuildResult() try: - c = compile(self.astTree, CQSCRIPT, 'exec') + self.set_param_values(params) + collector = BuildObjectCollector() + env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \ + .add_entry("build_object", collector.build_object).build() + + c = compile(self.ast_tree, CQSCRIPT, 'exec') exec (c, env) - if collector.hasResults(): + if collector.has_results(): result.set_success_result(collector.outputObjects) else: - raise ValueError("Script did not call build_object-- no output available.") + raise NoOutputError("Script did not call build_object-- no output available.") except Exception, ex: result.set_failure_result(ex) @@ -76,6 +96,7 @@ class BuildResult(object): def __init__(self): self.buildTime = None self.results = [] + self.first_result = None self.success = False self.exception = None @@ -85,6 +106,7 @@ class BuildResult(object): def set_success_result(self, results): self.results = results + self.first_result = self.results[0] self.success = True @@ -117,7 +139,7 @@ class InputParameter: self.name = None self.shortDesc = None self.varType = None - self.validValues = [] + self.valid_values = [] self.default_value = None self.ast_node = None @@ -136,15 +158,15 @@ class InputParameter: else: p.shortDesc = short_desc p.varType = var_type - p.validValues = valid_values + p.valid_values = valid_values return p def set_value(self, new_value): - if len(self.validValues) > 0 and not new_value in self.validValues: + if len(self.valid_values) > 0 and new_value not in self.valid_values: raise InvalidParameterError( "Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} " - .format( str(new_value), self.name, str(self.validValues))) + .format(str(new_value), self.name, str(self.valid_values))) if self.varType == NumberParameterType: try: @@ -181,7 +203,7 @@ class BuildObjectCollector(object): def build_object(self, shape): self.outputObjects.append(shape) - def hasResults(self): + def has_results(self): return len(self.outputObjects) > 0 @@ -190,10 +212,10 @@ class ScriptExecutor(object): executes a script in a given environment. """ - def __init__(self, environment, astTree): + def __init__(self, environment, ast_tree): try: - exec (astTree) in environment + exec ast_tree in environment except Exception, ex: # an error here means there was a problem compiling the script @@ -219,6 +241,10 @@ class InvalidParameterError(Exception): pass +class NoOutputError(Exception): + pass + + class ScriptExecutionError(Exception): """ Represents a script syntax error. @@ -258,6 +284,10 @@ class EnvironmentBuilder(object): self.env['__builtins__'] = env_dict return self + def with_cadquery_objects(self): + self.env['cadquery'] = cadquery + return self + def add_entry(self, name, value): self.env[name] = value return self diff --git a/doc/examples.rst b/doc/examples.rst index 2f97825..c2a4f38 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -43,7 +43,8 @@ Just about the simplest possible example, a rectangular box .. cq_plot:: - result = Workplane("front").box(2.0,2.0,0.5) + result = cadquery.Workplane("front").box(2.0,2.0,0.5) + build_object(result) .. topic:: Api References @@ -64,7 +65,7 @@ of a working plane is at the center of the face. The default hole depth is thro .. cq_plot:: result = Workplane("front").box(2.0,2.0,0.5).faces(">Z").hole(0.5) - + build_output(result) .. topic:: Api References @@ -533,6 +534,8 @@ with just a few lines of code. .rect(length-padding,height-padding,forConstruction=True) \ .vertices().cboreHole(2.4,4.4,2.1) + build_output(result) + Splitting an Object --------------------- diff --git a/tests/TestCQGI.py b/tests/TestCQGI.py index 925e7c4..6b213c7 100644 --- a/tests/TestCQGI.py +++ b/tests/TestCQGI.py @@ -92,7 +92,7 @@ class TestCQGI(BaseTest): build_object(h) """ ) - result = self._executeScriptWithParams(script, {'h': 33.33}) + result = cqgi.execute(script, {'h': 33.33}) self.assertEquals(result.results[0], "33.33") def test_that_assigning_string_to_number_fails(self): @@ -102,20 +102,37 @@ class TestCQGI(BaseTest): build_object(h) """ ) - with self.assertRaises(Exception): - result = self._executeScriptWithParams(script, {'h': "a string"}) + result = cqgi.execute(script, {'h': "a string"}) + self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError)) def test_that_assigning_unknown_var_fails(self): - script = textwrap.dedent( - """ + """ h = 20.0 build_object(h) """ ) - with self.assertRaises(cqgi.InvalidParameterError): - result = self._executeScriptWithParams(script, {'w': "var is not there"}) - def _executeScriptWithParams(self, script, params): - model = cqgi.CQModel(script) - return model.build(params) + result = cqgi.execute(script, {'w': "var is not there"}) + self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError)) + + def test_that_not_calling_build_object_raises_error(self): + script = textwrap.dedent( + """ + h = 20.0 + """ + ) + result = cqgi.execute(script) + self.assertTrue(isinstance(result.exception, cqgi.NoOutputError)) + + def test_that_cq_objects_are_visible(self): + script = textwrap.dedent( + """ + r = cadquery.Workplane('XY').box(1,2,3) + build_object(r) + """ + ) + + result = cqgi.execute(script) + self.assertTrue(result.success) + self.assertIsNotNone(result.first_result)