OpenSCAD tutorial — your first parametric part
OpenSCAD is a free, programmer-friendly 3D CAD modeller. You describe your model with a small script, OpenSCAD compiles it into a 3D mesh, and you can export it as STL or STEP for 3D printing and CAD. This tutorial walks through the language end-to-end by building a real parametric bracket — all of it runs in your browser, no install required.
Every snippet below works in our browser-based AI CAD Modeler. Open it in a second tab and paste as you read — or skip writing OpenSCAD entirely and let the agent generate the same code from a plain-English prompt.
What is OpenSCAD?
OpenSCAD is a script-based CAD tool. There is no drag-and-drop feature tree like Fusion 360 or SOLIDWORKS — instead you write code that describes shapes and how to combine them. Two consequences make this powerful:
- Everything is parametric by default. Variables become dimensions. Change one number, regenerate the whole model.
- Everything is text. Your CAD lives in git, code review, and any editor — no proprietary binary blobs.
The trade-off: there is no sketch tool, no constraint solver, and no "click a
face" UI. You compose models out of primitives
(cube, sphere, cylinder),
transformations (translate, rotate,
scale), and boolean operations
(union, difference, intersection).
That's almost the whole language.
Hello, cube
The smallest valid OpenSCAD program is a single primitive:
cube([30, 20, 10]);
That creates a 30 × 20 × 10 mm box at the origin. cube takes either
a single number (for a uniform-edge cube) or a 3-vector
[width, depth, height]. Add center=true to center it
on the origin instead of growing into the +X/+Y/+Z octant:
cube([30, 20, 10], center=true);
The three core 3D primitives
You will build almost everything from these three. Full reference: 3D primitives.
cube(size, center)
cube(10); // 10×10×10 cube
cube([30,20,10]); // rectangular box
cube(10, center=true); // centered on origin
sphere(r | d)
sphere(10); // radius 10 mm
sphere(d=20); // diameter 20 mm
sphere(10, $fn=64); // 64 facets
cylinder(h, r | d, center)
cylinder(h=20, r=5);
cylinder(h=20, d=10, center=true);
cylinder(h=20, r1=10, r2=2); // cone
polyhedron(points, faces)
// build any closed mesh from raw
// vertex + face data
polyhedron(
points=[[0,0,0],[10,0,0],
[5,10,0],[5,5,10]],
faces=[[0,1,2],[0,1,3],
[1,2,3],[2,0,3]]);
$fn, $fa, $fs facet variables.
OpenSCAD approximates curves with polygons. $fn sets the
exact number of facets; $fa the minimum angle per facet;
$fs the minimum segment size. Set $fn=64 globally
for smooth previews; the AI CAD agent sets sensible defaults for you.
Moving and rotating things
Transformations are modifiers: they apply to the next module or block that follows them. They do not end with a semicolon when they wrap a block. Full reference: Transformations.
translate([10, 0, 0]) cube(5); // move +X 10 mm
rotate([0, 0, 45]) cube(5); // rotate 45° around Z
scale([2, 1, 0.5]) cube(5); // non-uniform scale
mirror([1, 0, 0]) cube(5); // mirror across YZ plane
Chain them — they apply right-to-left:
translate([20, 0, 0])
rotate([0, 90, 0])
cylinder(h=10, d=4);
Wrap multiple primitives in { } to apply a transformation to
a group:
translate([0, 0, 5]) {
cube(10);
translate([5, 5, 0]) sphere(3);
}
Combining shapes with booleans
Booleans are the heart of OpenSCAD modeling. There are exactly three:
union()— merge children into one solid (default if you list shapes side-by-side).difference()— subtract every child after the first from the first. The first child is the base; the rest are holes.intersection()— keep only the volume shared by all children.
Drill a hole through a plate:
difference() {
cube([40, 40, 4]); // base plate
translate([20, 20, -1]) cylinder(h=6, d=6); // through-hole
}
If the subtracted cylinder ends exactly at the plate surface, OpenSCAD's mesher can produce a zero-area coplanar face which fails the manifold check. The PrintPal AI CAD agent adds this clearance automatically when it generates holes.
Variables & modules — making it parametric
Variables in OpenSCAD work like in any programming language but with one gotcha: they are immutable inside a scope (last assignment wins at parse time, not at runtime). Use them to name your dimensions:
plate_w = 40;
plate_t = 4;
hole_d = 6;
difference() {
cube([plate_w, plate_w, plate_t]);
translate([plate_w/2, plate_w/2, -1])
cylinder(h=plate_t+2, d=hole_d);
}
Wrap reusable geometry in a module. Modules are like functions
but they emit geometry instead of returning values:
module mounting_hole(d=3.2, depth=10) {
translate([0, 0, -1])
cylinder(h=depth+2, d=d);
}
difference() {
cube([40, 40, 4]);
for (x = [5, 35], y = [5, 35])
translate([x, y, 0]) mounting_hole();
}
Putting it together — an L-bracket
Now build something real. An M3 corner bracket with two mounting holes per leg:
// === Parameters ===========================
leg_length = 30; // [10 : 60]
leg_width = 20;
thickness = 4;
hole_d = 3.2; // M3 clearance
hole_inset = 6;
$fn = 48;
module hole_pair() {
for (x = [hole_inset, leg_length - hole_inset])
translate([x, leg_width/2, 0])
translate([0,0,-1]) cylinder(h=20, d=hole_d);
}
module leg() {
difference() {
cube([leg_length, leg_width, thickness]);
hole_pair();
}
}
union() {
leg(); // horizontal leg
rotate([0, -90, 0])
translate([0, 0, -thickness]) leg(); // vertical leg
}
That's a fully parametric printable bracket in < 30 lines. Change
leg_length and the holes reposition themselves; change
hole_d for a different screw size; change thickness
for a beefier or thinner part.
Loops, conditionals & list comprehensions
Need a hole pattern, a row of teeth, an array of bins? Loop over a range or list:
for (i = [0 : 5 : 30]) // start : step : end
translate([i, 0, 0]) cube(3);
for (p = [[0,0], [10,0], [5,10]]) // list of points
translate([p[0], p[1], 0]) cylinder(h=5, r=1);
if (thickness > 5)
cube([10, 10, thickness]);
else
cube([10, 10, 5]);
Exporting your model
In the PrintPal AI CAD Modeler, hit the Export menu in the top-right of the viewer. You get:
- .stl — binary STL for any slicer (Bambu Studio, PrusaSlicer, Cura, OrcaSlicer).
- .3mf — modern slicer-native bundle with embedded metadata.
- .step — real ISO-10303-21 AP214 BRep. Opens cleanly in Fusion 360, SOLIDWORKS, Onshape, FreeCAD, NX, and Creo.
- .off — text-based mesh for academic tools.
- .scad — the raw OpenSCAD source for further iteration.
What to read next
OpenSCAD cheat sheet
Every operator, primitive, and function on a single page — bookmark it.
Modules & functions
Write reusable parametric CAD libraries. The children() trick. BOSL2.
Parametric design guide
How to structure your variables so the AI agent (and future-you) can iterate.
Designing for 3D printing
Wall thickness, tolerances, overhangs — the rules every printable part follows.
Want to skip the typing?
Paste any of the snippets above into the AI CAD Modeler, or just describe the part in plain English and let the agent write the OpenSCAD for you.