Tutorial · 10 min read

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.

Beginner 10 min No install
You don't have to type any of this by hand.

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]]);
The $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
}
Always make holes 0.1–1 mm longer than the wall they cut.

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

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.

Open the CAD agent