Source code for tangible.backends.openscad

# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import, unicode_literals

from contextlib import contextmanager

from tangible import ast, utils


class Statement(object):

    def __init__(self, text, *args, **kwargs):
        self.suffix = kwargs.pop('suffix', ';')
        if kwargs:
            raise TypeError('invalid keyword arguments %r' % (kwargs.keys(),))
        if args:
            text = text.format(*args)
        self.text = text

    def render(self):
        return [self.text + self.suffix]


EmptyStatement = Statement('', suffix='')


class Block(object):

    def __init__(self, text, *args, **kwargs):
        self.prefix = kwargs.pop('prefix', '{')
        self.suffix = kwargs.pop('suffix', '};')
        if kwargs:
            raise TypeError('invalid keyword arguments %r' % (kwargs.keys(),))
        self.title = Statement(text, *args, suffix='')
        self.children = []
        self.stack = []

    def _get_head(self):
        if not self.stack:
            return self
        else:
            return self.stack[-1]

    def emptyline(self, count=1):
        for i in xrange(count):
            self._get_head().children.append(EmptyStatement)

    def statement(self, *args, **kwargs):
        self._get_head().children.append(Statement(*args, **kwargs))

    @contextmanager
    def block(self, *args, **kwargs):
        blk = Block(*args, **kwargs)
        self._get_head().children.append(blk)
        self.stack.append(blk)
        yield blk
        self.stack.pop(-1)

    def render(self):
        lines = self.title.render()
        if self.prefix:
            lines.append(self.prefix)
        for child in self.children:
            lines.extend(' ' * 4 + l for l in child.render())
        if self.suffix:
            lines.append(self.suffix)
        return lines


class Program(Block):

    def __init__(self):
        super(Program, self).__init__(None)
        self._preamble = set()

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        pass

    def preamble(self, item):
        self._preamble.add(item)

    def render(self):
        lines = list(self._preamble)
        if self._preamble:
            lines.append('')
        for child in self.children:
            lines.extend(child.render())
        return '\n'.join(lines)


[docs]class OpenScadBackend(object): """Render AST to OpenSCAD source code.""" def __init__(self, ast): """ :param ast: The AST that should be rendered. :type ast: Any :class:`tangible.ast.AST` subclass """ self.ast = ast
[docs] def generate(self): """Generate OpenSCAD source code from the AST.""" prgm = Program() BLOCK = prgm.block STMT = prgm.statement PRE = prgm.preamble SEP = prgm.emptyline def _generate(node): """Recursive code generating function.""" istype = lambda t: node.__class__ is t # Handle lists if istype(list): for item in node: _generate(item) # 2D shapes elif istype(ast.Circle): STMT('circle({0})', node.radius) elif istype(ast.Rectangle): STMT('square([{0}, {1}])', node.width, node.height) elif istype(ast.Polygon): points = map(list, node.points) #paths = map(list, node.paths) #if paths: # template = 'polygon(\npoints={!r},\n paths={!r}\n)' # STMT(template, points, paths) #else: STMT('polygon({0!r})', points[:-1]) elif istype(ast.CircleSector): PRE('module circle_sector(r, a) {\n' ' a1 = a % 360;\n' ' a2 = 360 - (a % 360);\n' ' if (a1 <= 180) {\n' ' intersection() {\n' ' circle(r);\n' ' polygon([\n' ' [0,0],\n' ' [0,r],\n' ' [sin(a1/2)*r, r + cos(a1/2)*r],\n' ' [sin(a1)*r + sin(a1/2)*r, cos(a1)*r + cos(a1/2)*r],\n' ' [sin(a1)*r, cos(a1)*r],\n' ' ]);\n' ' }\n' ' } else {\n' ' difference() {\n' ' circle(r);\n' ' mirror([1,0]) {\n' ' polygon([\n' ' [0,0],\n' ' [0,r],\n' ' [sin(a2/2)*r, r + cos(a2/2)*r],\n' ' [sin(a2)*r + sin(a2/2)*r, cos(a2)*r + cos(a2/2)*r],\n' ' [sin(a2)*r, cos(a2)*r],\n' ' ]);\n' ' };\n' ' }\n' ' }\n' '};') STMT('circle_sector({0}, {1})', node.radius, node.angle) # 3D shapes elif istype(ast.Cube): STMT('cube([{0}, {1}, {2}])', node.width, node.depth, node.height) elif istype(ast.Sphere): STMT('sphere({0})', node.radius) elif istype(ast.Cylinder): STMT('cylinder({0}, {1}, {2})', node.height, node.radius1, node.radius2) elif istype(ast.Polyhedron): points = map(list, node.points) triangles = map(list, node.triangles) if node.triangles else [] if node.quads: triangles.extend(utils._quads_to_triangles(node.quads)) template = 'polyhedron(\npoints={0!r},\n triangles={1!r}\n)' STMT(template, points, triangles) # Transformations elif istype(ast.Translate): with BLOCK('translate([{0}, {1}, {2}])', node.x, node.y, node.z): _generate(node.item) elif istype(ast.Rotate): with BLOCK('rotate({0}, {1!r})', node.degrees, list(node.vector)): _generate(node.item) elif istype(ast.Scale): with BLOCK('scale([{0}, {1}, {2}])', node.x, node.y, node.z): _generate(node.item) elif istype(ast.Mirror): with BLOCK('mirror({0!r})', list(node.vector)): _generate(node.item) # Boolean operations elif istype(ast.Union): with BLOCK('union()'): _generate(node.items) elif istype(ast.Difference): with BLOCK('difference()'): _generate(node.items) elif istype(ast.Intersection): with BLOCK('intersection()'): _generate(node.items) # Extrusions elif istype(ast.LinearExtrusion): with BLOCK('linear_extrude({0}, twist={1})', node.height, node.twist): _generate(node.item) elif istype(ast.RotateExtrusion): with BLOCK('rotate_extrude()'): _generate(node.item) _generate(self.ast) return prgm.render()