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:
dnotdiam,tnotthick— 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
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.
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.