Ao is a tool for programmatic computer-aided design.


If you prefer visual explanations, start by watching a 4-minute demo.

In Ao, solid models are defined as Scheme scripts, and there are no opaque function calls into the geometry kernel: everything is visible to the user.

What does that mean? To make a sphere in OpenSCAD, you would write:

sphere(2); // Where does this function come from?

The primitive shape sphere is an opaque call into the kernel, and users can go no further. The library of basic shapes is delimited by the primitives provided by the language.

Let's compare to drawing a sphere in Ao from first principles:

(define (sphere r)
    (lambda ( x y z )
        (- (sqrt (+ (* x x) (* y y) (* z z))) r)))
(define s (sphere 2))
(ao-show s)

In most scripting systems, there's a clear barrier between user-controlled code and calls into the geometry kernel. In Ao, that barrier is pushed so far back as to be effectively invisible: ao-show accepts a lambda function and draws it on the screen.

You can think of Ao as a homoiconic kernel: even fundamental, primitive shapes are represented as code in the user-level language. It's turtles all the way down.


Ao is run as a command-line application that embeds GNU Guile. Models can be created in the command line or in standalone scripts.


The screenshow above shows the command line on the left, a text editor on the bottom right, and the 3D viewport on the top right.

To get started, you need to know three functions:

To try it out for yourself, clone the source from Github and build it.

Formats and Exports

Models are described in one or more Scheme scripts. At the moment, there's no way to work with existing models (like .stl files), since they're a fundamentally different representation.

In a concession to the outside world, Ao does have a few export functions:

Ever wonder what you'd get if you cut a sphere from a Menger sponge? Here's an example designed in Ao, exported as a mesh, and rendered in Blender: Exported mesh Notice that the corners and edges are sharp, unlike what you would see with a marching cubes triangulation.

Blog posts

Representation and JIT
Shapes are represented as lambda functions, but Scheme isn't designed for the manyfold evaluation needed in an f-rep kernel. How does the kernel turn a Scheme function into a form that can be rendered quickly?

Affine coordinates
Nested transforms lead to deep expression trees. Can we do better? This post describes a strategy for keeping trees small by automatically detecting and combining affine transforms.

Bounds and transforms
Applying a transform to a shape will change its bounding box, but hard-coding the bounds transform is tedious. How can we automatically derive a new bounding box for the transformed shape?