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.
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);
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]);
}    
Further Reading
- I read Programming with OpenSCAD which is quite good but for programmers a bit basic/slow.
 - LLMs work quite well
 
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:
| Task | Description | Code Example | 
|---|---|---|
| Create a Cube | Draws 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 Sphere | Draws a 3D sphere. The r argument specifies the radius. | sphere(r = 10); | 
| Create a Cylinder | Draws a 3D cylinder. h is height, r is radius (r1 and r2 for a cone). | cylinder(h = 20, r = 5); | 
| Create a Polyhedron | Defines 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 Square | Creates a 2D square or rectangle. | square([15, 10]); | 
| Draw a 2D Circle | Creates a 2D circle. The r argument is the radius. | circle(r = 10); | 
| Draw a 2D Polygon | Defines a 2D shape by listing the points of its outline. | polygon(points=[[0,0],[10,5],[5,15]]); | 
| Extrude to 3D | Turns a 2D shape into a 3D solid by extruding it along the Z-axis. | linear_extrude(height = 10) circle(r = 5); | 
| Revolve to 3D | Creates 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 Intersection | Keeps only the overlapping parts of multiple shapes. | intersection() { cube(10); sphere(r=7); } | 
| Move an Object | Translates (moves) an object along the X, Y, and Z axes. | translate([10, -5, 20]) cube(5); | 
| Rotate an Object | Rotates 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 Object | Resizes an object by a scaling factor for each axis. | scale([1.5, 1, 0.8]) sphere(r=10); | 
| Create a Mirror Image | Mirrors an object across a plane defined by a vector. | mirror([1, 0, 0]) translate([5,0,0]) cube(5); | 
| Create Rounded Hull | Creates 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 Minkowski | Adds the volumes of two shapes, useful for rounding edges. | minkowski() { cube(10); sphere(r=2); } | 
| Define a Variable | Stores a value (number, vector, string, boolean) that can be reused. | box_width = 25; cube([box_width, 10, 10]); | 
| Create a Loop | Iterates over a range or vector, creating an object at each step. | for (i = [0:4]) translate([i*15, 0, 0]) cube(10); | 
| Use a Conditional | Chooses which geometry to create based on a boolean condition. | if (use_sphere) { sphere(10); } else { cube(10); } | 
| Create a Reusable Module | Defines a new, reusable object that can take parameters, similar to a function. | module my_box(w, d, h) { cube([w,d,h]); } | 
| Call a Module | Instantiates an object defined by a module. | my_box(10, 20, 5); | 
| Module with Children | Creates 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 Function | Creates 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 Text | Generates 3D text from a string. | linear_extrude(5) text("Hello", font="Liberation Sans"); |