3D Forms

Overview

Explore building three-dimensional forms with OpenSCAD, a functional programming language for constructive solid geometry modeling.

Tools

OpenSCAD

3D Forms

It is not hard to understand modern art. If it hangs on a wall it’s a painting, and if you can walk around it it’s a sculpture.

Tom Stoppard, author of Rosencrantz & Guildenstern are Dead

Most of the favorite subjects of representational art—human figures, landscapes, still lifes—are three-dimensional forms. Paintings and drawings represent these forms by projecting them into two dimensions. This is a destructive process in which part of the form is lost. You cannot walk around a painting to see its subject from the other side. Where a painting strives to convey a sense of scale, volume, substance, and presence, a sculpture cannot escape them.

Onscreen, 3D graphics exist between painting and sculpture. You can rotate computer-generated 3D shapes, but you are still seeing a 2D projection. 3D Printing brings these shapes into the physical world. Getting “real” results from procedural generation can be very gratifying, providing physical evidence of your efforts. While not physical, VR is also a compelling medium for computational form. I especially enjoy combining the rapid iteration enabled by working procedurally with the sense of presence and scale afforded by virtual reality. It is an intensely creative experience to procedurally generate a tree and immediately stand in its shadow looking up at the sky through its branches.

New York Apartment, 2020
Tega Brain
New York Apartment, 2020
This 3D structure is part of a project that combines the totality of New York real estate into a single listing. The structure is generated from an amalgamation of extruded floor plans.
Plan3tary Bodies, 2015
Lee Blalock
Plan3tary Bodies, 2015
Images of the body are reimagined as planetary bodies through 3D modeling, as inspired by NASA images.
Vase Forms, 2019
Andy Lomas
Vase Forms, 2019
Andy Lomas modeled natural systems of cellular growth to create these vase-like forms.
Free Universal Construction Kit, 2012
F.A.T. Lab
Free Universal Construction Kit, 2012
A collection of adapters that allows you to join different construction toys together, such as Lego, Duplo, Zoob, and more.
Open Source Afro Hair Library, 2021
A.M. Darke
Open Source Afro Hair Library, 2021
A.M. Darke is cultivating this feminist, anti-racist database for 3D models of Black hair textures and styles.

OpenSCAD

OpenSCAD is a language for specifying procedural 3D forms using constructive solid geometry. CSG is a modeling technique in which complex shapes are created by combining simple shapes using boolean operations like union, difference, and intersection. It is well-suited to designing mechanical parts for manufacturing, but not well-suited for organic shapes or characters.

OpenSCAD

Hello, OpenSCAD!

Download OpenSCAD, set it up, and try to recreate the images below. Tip: Use code to rotate the bar.

green bar yellow ring

OpenSCAD is not like C

At first glance, OpenSCAD syntax looks similar to C or JavaScript, with braces, brackets, and semicolons appearing about where you would expect. But as you read the code you see some surprising differences.

Look at the example below, which creates a ring by subtracting one cylinder from another.

difference() {
    cylinder (h=1, r=4, center = true);
    cylinder (h=1, r=3, center = true);
}

In the example, difference() looks somewhat like a function definition and it works somewhat like a function. It isn’t really either. In the vocabulary of OpenSCAD difference() is an operator and cylinder(...); is an action. Operators act on actions or groups of actions in braces.

These differences reflect the fundamentally different approach to programming in OpenSCAD compared to C.

Functional vs. Procedural

The OpenSCAD Manual describes OpenSCAD as a functional programming language. One could also more generally refer to OpenSCAD as a declarative language. In contrast to JavaScript, C, and Processing, it is not an imperative or procedural language.

Classifying programming languages by paradigm is tricky in practice. The boundaries of paradigms are not always clear. Different paradigms have different primary concerns and may be orthogonal to each other, and many languages support multiple paradigms.

paradigm description
imperative focuses on the steps needed to achieve a goal; ordered commands, mutable program state
↳ procedural an imperative approach that primarily organizes commands using procedures
↳ object-oriented an imperative approach that groups related data and procedures using objects
declarative focuses on what you want to achieve rather than the steps to achieve it; unordered
↳ functional a declarative approach that organizes logic using pure functions with immutable states and no side effects
↳ logic a declarative approach in which a program is a collection of logical declarations from which facts can be inferred

Immutable vs. Mutable Data

The most noticeable effect of OpenSCAD being functional is that data in OpenSCAD is immutable: the value of every variable is constant. In fact, variables are not even assigned values at runtime. A variable’s value is determined and assigned at compile time before the script is run. Since variables are constant, you should only assign a variable a value one time. If you do assign to a variable more than once, the variable’s value will be the last assigned value, which is pretty confusing.

a = 1;
echo(a); // -> 2
a = 2;
echo(a); // -> 2

This takes some getting used to. It helps to keep in mind that variables are constant and given their value before the script is run. The OpenSCAD manual goes into more detail about how variables behave.

Prefix vs. Infix Notation

OpenSCAD’s boolean and transformation operators appear in code before their operands. This is called prefix notation. OpenSCAD’s mathematic operators appear between their operands. This is called infix notation.

Infix notation probably looks familiar. It is how arithmetic is notated, and is used in the most popular programming languages.

Javascript expression with infix notation

2 * 3 -> 6
2 * 3 * 4 -> 24
2 * (3 + 4) -> 14

Prefix notation probably doesn’t look familiar, though it is used in some important programming languages.

Lisp expressions with prefix notation

(* 2 3) -> 6
(* 2 3 4) -> 24
(* 2 (+ 3 4)) -> 14

While the example above looks unusual if you are used to C or JavaScript, prefix notation isn’t really that unusual. Compare infix notation to calling a function in JavaScript. The name of the function comes first, followed by the parameters.

multiply(2, 3) -> 6
multiply(2, 3, 4) -> 24
multiply(2, add(3, 4)) -> 14

You can think of OpenSCAD’s boolean and transform operations as prefix operators or function calls. Unlike JavaScript and C, the contents of {} are operands or parameters rather than statements.

union() {
    cylinder (h=4, r=1, center = true);
    rotate ([90,0,0]) cylinder (h=4, r=1, center=true);
}

Named vs. Positional Parameters

OpenSCAD supports both positional and named parameters.

OpenSCAD with named parameters

cylinder(h = 4, r = 1, center = true);

OpenSCAD with positional parameters

cylinder(4, 1, 1, true);

Javascript emulating named params with an object literal

cylinder({ h: 4, r: 1, center: true });

function cylinder(options) {
  let h = options.h;
  let r = options.r;
  let center = options.center;
}

OpenJSCAD

OpenSCAD’s functional model has its advantages, but imperative languages also have advantages. For one, imperative languages are more familiar to most programmers. To explore constructive solid geometry modeling in an imperative style, you may want to use one OpenJSCAD.

Study Examples

These examples will build up to a simplified parametric Lego brick, introducing many features of OpenSCAD along the way. To get started we’ll need the dimensions of a Lego brick.

Generating Primitives

$fn = 20;
% cube([8,8,9.6], true);
cylinder(h=1.8, r=2.4, center=true);

example_1

$fn, $fa, $fs

$fn, $fa, and $fs are special variables that control how many edges are used for drawing arcs and circles. In the example above $fn is set to 20 telling OpenSCAD to make the cylinder with 20 sides. If $fn is 0 OpenSCAD will calculate the number of sides using a minimum angle $fa between edges and a minimum edge length $fs.

%, #, *, !

The %, #, *, and ! modifier characters can alter how parts of your drawing are rendered. These can be used to ghost, highlight, disable, or isolate a shape or subtree respectively.

In the example above the cube is ghosted or backgrounded. It is shown in transparent gray but isn’t a real part of the rendered geometry.

echo

OpenSCAD has an echo() statement for tracing out debugging info.

Transformations

$fn = 20;

color("SlateBlue") {
    cube([8,8,9.6], true);
}

translate([0, 0, 9.6 * .5 + 1.8 * .5]) {
    cylinder(h=1.8, r=2.4, center=true);
}

example_2

Translate() moves the shapes passed to it. Here, translate moves the cylinder up. Notice that the braces are used to group parameters to translate. You can also scale, resize, rotate, and mirror shapes.

The example also demonstrates using color to change the color used to render the cube. Use color to make it easier to understand your model.

Boolean Operations

$fn = 20;

difference() {
    union() {
        cube([8,8,9.6], true);
        translate([0,0,9.6 * .5 + 1.8 * .5]) {
            cylinder(h=1.8, r=2.4, center=true);
        }
    }
    translate([0,0, -9.6 * .5 + 1.8 * .5]) {
        cylinder(h=1.8, r=2.4, center=true);
    }
}

example_3

This example uses difference() and union() to combine shapes. A cylinder and cube are combined with union() and a recess is cut out of the resulting shape using difference(). OpenSCAD also has an intersect() operator.

OpenSCAD may not preview this shape very cleanly. Render the shape with Main Menu > Design > Render to get a clear view of the rendered geometry.

Variables + Modules

$fn = 20;
brick_width = 8;
brick_height = 9.6;
knob_radius = 2.4;
knob_height = 1.8;

module unit_brick () {
    difference() {
        union() {
            cube([brick_width, brick_width, brick_height], true);
            translate([0, 0, (brick_height + knob_height) * .5]) {
                cylinder(h=knob_height, r=knob_radius, center=true);
            }
        }
        translate([0,0, (-brick_height + knob_height) * .5]) {
            cylinder(h=knob_height, r=knob_radius, center=true);
        }
    }
}

unit_brick();

This example produces the same shape as the example above, but uses variables and a module definition to make the code easier to read.

For Loops

$fn = 20;
brick_width = 8;
brick_height = 9.6;
knob_radius = 2.4;
knob_height = 1.8;
rows = 2;
columns = 8;


module unit_brick () {
    difference() {
        union() {
            cube([brick_width, brick_width, brick_height], true);
            translate([0, 0, (brick_height + knob_height) * .5]) {
                cylinder(h=knob_height, r=knob_radius, center=true);
            }
        }
        translate([0,0, (-brick_height + knob_height) * .5]) {
            cylinder(h=knob_height, r=knob_radius, center=true);
        }
    }
}


for (x = [0:columns-1], y = [0:rows-1]) {
    translate([x * brick_width, y * brick_width, 0]) {
        unit_brick();
    }
}

example_4

This example uses for to create several instances of our basic shape. OpenSCAD’s for() looks a lot like the imperative flow control structure, but works differently because OpenSCAD is functional.

The for loop creates a union of multiple instances of the following group. Each instance is constructed in its own scope and no data can flow between scopes.

The OpenSCAD for syntax allows “iterating” over multiple variables at once. This example will create a unit brick for every combination of x and y. In JavaScript, you would use a nested pair of loops to achieve a similar effect.

Coding Challenges

Try creating OpenSCAD scripts for each of these shapes.

barbell tag pipes ring

Build this shape so that you can parametrically control the number of disks that make up the ring.

Parametric OpenSCAD

Because OpenSCAD is a language, not an interactive modeler, OpenSCAD files fully specify the modeling process rather than just the resulting geometry. This makes OpenSCAD very well-suited to parametric designs—flexible designs that a user can customize by adjusting parameters. Thingiverse uses OpenSCAD to allow users to share and customize 3D printable objects.

Digital Fabrication

Digital fabrication is a prototyping and production workflow that combines computer-aided design with computer-controlled manufacturing techniques. Just as desktop publishing caused shifts in skills, methods, and equipment needed to print documents, digital fabrication is changing how objects are designed and built.

Machining is a subtractive manufacturing technique in which an object is carved out of a block of material. Computer numerical control (CNC) milling and lathing machines use subtractive manufacturing to make highly precise and very strong parts.

3D printing refers to a range of additive fabrication techniques. In additive fabrication, an object is built up, usually in layers. Because subtractive methods can only create shapes that allow the cutting tool to reach all the material that must be removed, additive fabrication can achieve shapes that subtractive manufacturing cannot. There are many types of 3D printing in use.

Fused Deposition Modeling (FDM) + Fused Filament Fabrication (FFF)
Thermoplastic is fed from a spool through a heated extruder.
Stereolithography (SLA)
Photopolymer resin is cured via an ultraviolet laser.
Selective Laser Sintering (SLS)
Powdered material is fused by a laser.
Laminated Object Manufacturing (LOM)
Layers of the object are cut from paper or film.

Slicing and G-Code

The most popular hobbyist 3D printing method is Fused Filament Fabrication or Fused Deposition Modeling (FDM). To print an OpenSCAD file on an FDM machine you need to export the rendered geometry and then “slice” it with software like Cura or Slic3r. This software converts your 3D geometry into the tool paths your 3D printer will need to execute to build your part.

Under the hood, the tool path is described using G-code. G-code files are a list of instructions for CNC machines like 3D printers and mills. Typically, you don’t really need to know anything about G-code. Your slicer does the work for you. But learning a little about G-code is a good idea if you want to modify or build your own machines.

This g-code excerpt sets the move speed to 100 mm/minute and then moves the machine head to 0,0.

G0 F100;
G1 X0 Y0;

You don’t need to know g-code to use a 3D printer: you simply convert your model to g-code with a slicer app and load it on your printer. The app generates the g-code based on the object you are printing, the material you are using, and the specs of your machine. You can write g-code directly if you want: g-code files are plain text and relatively easy to understand. Writing your own g-code allows you to directly control your hardware, which can be useful for custom applications and machines like drawbots.

Raven Greg Schomburg and I created this custom drawbot project. It is controlled with g-code generated by a custom Processing app.

Other Procedural 3D Tools

Support for procedural methods is common in 3D software. Nearly all CAD software has support for expressing design constraints and parameters. CG modeling software often hosts a runtime for executing scripts. These scripts are often used to add functionality to the UI, automate tasks, or procedurally generate 3D content.

Keep Sketching!

Sketch

Explore using OpenSCAD, OpenJSCAD, or another tool to procedurally generate 3D shapes. Consider exporting your shapes and working with them further in a 3D package such as Cinema 4D, Maya, After Effects, or even Photoshop.

Challenge: Castle

Build a 3D castle! Start with a reference search and look at examples of real and fantasy castles. Castles have many interesting features to consider: towers, spires, moats, walls, crenelations, bricks, stairs, windows, etc.

Consider using parameterization or randomization in your script to get more variety—but it’s okay to hard code the challenge too.

Explore