Reference

Modules & functions

Modules and functions are how you turn ad-hoc OpenSCAD scripts into reusable libraries. Modules emit geometry, functions return values — they look similar but they do different things and you'll use both constantly.

Defining modules

module name(params...) {
  // geometry-producing statements
}

Modules behave like user-defined geometry. Call them like any built-in primitive. Parameters can be positional or named, and any parameter can have a default.

module washer(od=10, id=3.2, t=1.6) {
  difference() {
    cylinder(h=t, d=od, $fn=48);
    translate([0,0,-1]) cylinder(h=t+2, d=id, $fn=32);
  }
}

washer();                          // defaults
washer(od=20, id=8, t=3);        // custom

Children: modules that take other modules

Modules can accept geometric children — the geometry passed between the call's braces. Inside the module, use children() to emit them, or children(i) for the i-th one. The special variable $children holds the count.

module array_x(n=5, spacing=10) {
  for (i = [0:n-1])
    translate([i * spacing, 0, 0])
      children();
}

array_x(6, 12) cylinder(h=8, d=6);

// Pass multiple children — use children(i) to pick one
module alternate() {
  translate([ 0, 0, 0]) children(0);
  translate([15, 0, 0]) children(1);
}
alternate() { cube(8); sphere(4, $fn=32); }
children() is the secret to building DSL-like APIs.

Wrap any geometry in a positioning module (polar_array, fillet_corners, place_on_face) and call it like a transformation: polar_array(n=8, r=20) my_cog_tooth();

Defining functions

function name(params...) = expr;

Functions are pure expressions — they take values, return a value (number, vector, string, list). They cannot produce geometry; use a module for that. Function bodies are single expressions (no braces) but can use let for intermediate bindings.

function in_to_mm(i) = i * 25.4;
function circle_pts(r, n=36) =
  [for (i = [0:n-1])
     [r * cos(i*360/n), r * sin(i*360/n)]];

polygon(circle_pts(20, 8));    // octagon!

Recursion

OpenSCAD has no loops inside a function body — so anything iterative becomes recursion or a list comprehension. List comprehensions cover the vast majority of real-world cases; recursion is the escape hatch.

// Factorial — recursive (uncommon in practice)
function fact(n) = (n <= 1) ? 1 : n * fact(n - 1);
echo(fact(5));   // 120

// Same thing as a list comprehension (idiomatic)
function fact_lc(n) =
  len(n <= 1 ? [1] : [for (i=1, a=1;
       i <= n;
       a=a*i, i=i+1) a]);

Patterns for libraries

Naming conventions

  • Modules: snake_case_noun — they emit a thing. screw_hole, bracket_leg, hex_grid.
  • Functions: snake_case_verb_or_noun — they compute a value. fact(n), circle_pts(r, n), in_to_mm(i).
  • Module parameters that mirror their dimension: d not diam, t not thick — match the conventions of the built-ins.

Parameter objects

For modules that take many related parameters, pass a vector instead:

// [length, width, thickness, hole_d, hole_inset]
M3_BRACKET = [30, 20, 4, 3.2, 5];

module bracket(p) {
  difference() {
    cube([p[0], p[1], p[2]]);
    translate([p[4], p[1]/2, -1])
      cylinder(h=p[2]+2, d=p[3], $fn=24);
  }
}

bracket(M3_BRACKET);

External libraries

Pull in someone else's modules with use (functions/modules only) or include (everything, including variables):

use <BOSL2/std.scad>       // the de facto OpenSCAD standard library
use <BOSL2/gears.scad>
use <MCAD/involute_gears.scad>
use <threads.scad>            // dotscad/threads

gear(teeth=24, mod=2, thickness=4);     // from BOSL2
BOSL2 (Belfry OpenSCAD Library 2) is the go-to library.

Hardware (screws, nuts, washers), gears, threads, splines, attachables, masks for fillets/chamfers, and a richer math toolkit. The AI CAD agent will reach for BOSL2 idioms when generating mechanical parts.

BOSL2 and MCAD ship with the in-browser editor.

use <BOSL2/std.scad>, use <BOSL2/gears.scad>, use <MCAD/involute_gears.scad>, and every other file inside those two libraries works out of the box in the AI CAD Modeler. The library source is lazy-fetched the first time you reference it (≈ 1 MB gzipped for BOSL2, ≈ 50 KB for MCAD) and mounted into the WASM filesystem at /BOSL2 and /MCAD. Cached for the rest of your session, so iteration is instant.

Other libraries (NopSCADlib, Round-Anything, dotscad/threads, your own private .scad files) aren't bundled — inline the modules you need, or open the file in desktop OpenSCAD.

Modules as features in the AI CAD agent

The PrintPal AI CAD agent annotates user-defined modules with a // @feature: name comment so you can reference them stably across turns of the conversation. Saying "make @feature[corner_holes] 1 mm bigger" rewrites only that module, not the rest of your file.

// @feature: corner_holes
module corner_holes(d=3.2) {
  for (x = [inset, leg_length - inset])
    translate([x, leg_width/2, -1])
      cylinder(h=thickness+2, d=d, $fn=24);
}

See the agent guide for the full @feature workflow.

Let the agent write the modules for you

"Bracket with parametric leg length, mounting holes, and a stiffening rib" — the agent picks the right modules, names them, and tags each with a @feature for stable references.

Open the agent