OpenSCAD: 3D Printing for Developers

I've previously written about and run a workshop on OpenJSCAD, which is a JavaScript reimagining of OpenSCAD. But I've recently been exploring the OG OpenSCAD.

OpenSCAD MacOS

Basics

The interface looks a little dated (the main release was about 4 years ago!) but the basic functionality works very nicely. It is declarative, which should now seem familiar to UI developers. To draw a cuboid you simply write:

cube([10, 20, 30]);

giving it a width, depth and height in an array. You end the line with a semicolon. You can apply operations such as translate like:

translate([5,5,5]) cube(5);

on one line, or group with curly brackets, which works nicely with more than one object:

translate([5,5,5]) {
  cube(5);
  translate([-5,5,5]) sphere(5);
} 

For loops exist and can be nested, for example:

for(a = [0:15:360]) {
    rotate([0, a, 0])
    for(i = [1:1:10]) {
        translate([i * 5, i * i / 2, 0])
        color([i/12, a/360, 0.3]) 
        cube(i, true);
    }
}

This also sets the colour with color.

Functions (Modules)

You can build reusable functions (modules) with module. Let's immediately look at something non-trivial, a Lego-style building brick. Modules take parameters which can have defaults.

module lego_box(studs_x = 4, studs_y = 2, base_height_plates = 3) {
  local_stud_diameter = 5;
  local_stud_height = 1.8;
  local_stud_spacing = 8;
  local_plate_height = 3.2;
  local_wall_thickness = 1.6;

  box_width = studs_x * local_stud_spacing;
  box_depth = studs_y * local_stud_spacing;
  box_height = base_height_plates * local_plate_height;

  // --- Main Body and Studs ---
  union() {
    cube([box_width, box_depth, box_height]);

    for (x = [0 : studs_x - 1]) {
      for (y = [0 : studs_y - 1]) {
        translate([
          (x * local_stud_spacing) + (local_stud_spacing / 2),
          (y * local_stud_spacing) + (local_stud_spacing / 2),
          box_height
        ]) {
          cylinder(h = local_stud_height, r = local_stud_diameter / 2, $fn = 64);
        }
      }
    }
  }
}

// Create a standard 4x2 Lego brick
lego_box(4, 2);
A construction toy like brick

Children

A bit like React you can have children. This is implicitly set up (you don't declare a parameter), but can then be used by calling children() in the body of the module.

Let's build a grid module that will replicate the children().

module grid(n,m) {
    for(i = [1:n]) {
        for(j = [1:m]) {
            translate([i,j,0]) {
                children();
            }
        }
    }
}

grid(4,4) {
    cube([0.5,0.5,0.5]);
}    
A grid module

Further Reading

How do I ...

Here are some common tasks in OpenSCAD and how to do them. It covers some of the things I didn't above like 2D shapes, ways of combining shapes, text and more:

TaskDescriptionCode Example
Create a CubeDraws a 3D rectangular prism. The argument can be a single number for a cube or a vector [width, depth, height].cube([10, 20, 5]);
Create a SphereDraws a 3D sphere. The r argument specifies the radius.sphere(r = 10);
Create a CylinderDraws a 3D cylinder. h is height, r is radius (r1 and r2 for a cone).cylinder(h = 20, r = 5);
Create a PolyhedronDefines a 3D shape by specifying its vertices (points) and the faces that connect them.polyhedron(points=[[0,0,0],[10,0,0],[5,10,5]], faces=[[0,1,2]]);
Draw a 2D SquareCreates a 2D square or rectangle.square([15, 10]);
Draw a 2D CircleCreates a 2D circle. The r argument is the radius.circle(r = 10);
Draw a 2D PolygonDefines a 2D shape by listing the points of its outline.polygon(points=[[0,0],[10,5],[5,15]]);
Extrude to 3DTurns a 2D shape into a 3D solid by extruding it along the Z-axis.linear_extrude(height = 10) circle(r = 5);
Revolve to 3DCreates a 3D shape by rotating a 2D shape around the Z-axis.rotate_extrude(angle = 360) translate([10, 0, 0]) square(5);
Combine Shapes (Union)Merges multiple shapes into a single object.union() { cube(10); sphere(r=7); }
Subtract Shapes (Difference)Subtracts the second (and subsequent) object(s) from the first.difference() { cube(10); sphere(r=7); }
Find IntersectionKeeps only the overlapping parts of multiple shapes.intersection() { cube(10); sphere(r=7); }
Move an ObjectTranslates (moves) an object along the X, Y, and Z axes.translate([10, -5, 20]) cube(5);
Rotate an ObjectRotates an object around the X, Y, and Z axes by a given number of degrees.rotate([45, 0, 90]) cube([10, 20, 5]);
Scale an ObjectResizes an object by a scaling factor for each axis.scale([1.5, 1, 0.8]) sphere(r=10);
Create a Mirror ImageMirrors an object across a plane defined by a vector.mirror([1, 0, 0]) translate([5,0,0]) cube(5);
Create Rounded HullCreates a convex hull that connects the child objects, effectively "wrapping" them.hull() { translate([0,0,0]) sphere(5); translate([20,0,0]) sphere(5); }
Create Rounded MinkowskiAdds the volumes of two shapes, useful for rounding edges.minkowski() { cube(10); sphere(r=2); }
Define a VariableStores a value (number, vector, string, boolean) that can be reused.box_width = 25; cube([box_width, 10, 10]);
Create a LoopIterates over a range or vector, creating an object at each step.for (i = [0:4]) translate([i*15, 0, 0]) cube(10);
Use a ConditionalChooses which geometry to create based on a boolean condition.if (use_sphere) { sphere(10); } else { cube(10); }
Create a Reusable ModuleDefines a new, reusable object that can take parameters, similar to a function.module my_box(w, d, h) { cube([w,d,h]); }
Call a ModuleInstantiates an object defined by a module.my_box(10, 20, 5);
Module with ChildrenCreates a module that can modify other objects passed into it.module place_in_a_row() { for(i=[0:2]) translate([i*15,0,0]) children(); }
Use children()A command within a module that refers to the objects passed to it.place_in_a_row() sphere(5);
Define a FunctionCreates a reusable calculation that returns a value (not geometry).function hypotenuse(a, b) = sqrt(a*a + b*b);
Control Resolution ($fn)Sets the number of fragments (facets) for curved surfaces. Higher is smoother.sphere(r=10, $fn=100);
Control Resolution ($fa)Sets the minimum angle for a fragment. Overrides $fn.cylinder(h=10, r=5, $fa=1);
Control Resolution ($fs)Sets the minimum size of a fragment. Overrides $fn.sphere(r=10, $fs=0.1);
Create TextGenerates 3D text from a string.linear_extrude(5) text("Hello", font="Liberation Sans");