Getting Started with OpenSCAD

Table of Contents

Introduction

The content covered in this page can be summed up here: https://openscad.org/cheatsheet/. But if you’re unfamiliar with OpenSCAD, it likely won’t be the best way to introduce yourself. That said it is an excellent reference that I almost always have up while I’m doing work in the software.

The concept behind OpenSCAD is that your 3D model is defined as a script. That’s actually what the S stands for in OpenSCAD: Open Scriptable Computer Aided Design. By using basic shapes and transformations, you can design all sorts of neat stuff. The input code shares the same syntax as the C programming language.


Download & Installation

Information on downloading and installing OpenSCAD can be found here: https://openscad.org/downloads.html

You likely want to use the 64 bit version. Leave all of the installation options at their default.


Primitive Objects

Our bread and butter shapes for OpenSCAD are cubes, spheres, and cylinders. These primitive shapes are what we can use to define almost any model we’ll need. There are several other shape methods (both 2D and 3D), but 95% of the time I’ve been able to get by without using them.

The Cube

The name “cube” is a little misleading, because a cube’s sides are all equal, and all of he corners are 90 deg. This is really more of a “rectangular prism” function, but I’m assuming that the OpenSCAD developers though that would be arduous to write over and over. But yes – the cube command creates a 6-sided mesh that can be variable sizes in the X, Y, and Z dimension. The syntax to create a cube is:

cube([width,depth,height], center);

Where:

  • cube: the name of the function that we’re invoking
  • width: How far the mesh stretches in the positive X-dimension
  • depth: How far the mesh stretches in the positive Y-dimension
  • height: How far the mesh stretches in the positive Z-dimension
  • center: an optional argument that is false by default. If you set “center = true”, the mesh will be generated centered on the origin along each axis. Otherwise (by default) the mesh will have a corner at the origin and extend in the positive direction for each axis.

So if I wanted to make a cube that was 10mm long, 20 wide, and 3 high, I might invoke a command like this (note I’m not centering it)

cube([10, 20, 3]);

And that ends up looking like this:

And if we decided for some reason that we wanted to center it using the following code:

cube([10, 20, 3], center = true);

The result would be:

The Cylinder

Unlike the cube, the cylinder is is accurately named – It creates a cylinder. However there are modifications built in that we can use to transform it to a conic section, which can be useful. The syntax looks like:

cylinder(h,r|d,center);

Where:

  • cylinder: The name of the function
  • h: the height of the cylinder (in the z axis)
  • r|d: There are two options here, you can either use r = value, or d = value. r indicates you’re setting the radius of the cylinder. d indicates the diameter (2r) of the cylinder.
  • center: This is an optional parameter – if you set it to true, the cylinder will spawn in the center of the XYZ origin. Otherwise it will be centered along the XY origin, but it’s bottom surface will be on the Z=0 surface.

So if I want to make a pop can shape that’s 50 mm wide, and a height of 100 mm, I’d create it with:

cylinder(100, d = 50);

And it would look like:

Centering the cylinder would just shift it downwards along the Z-axis.

Now I know what you’re thinking, “Shane – it’s all, like, edgy or something. I want my cylinders to be perfectly round!” To which my response is, be patient, I’ll get to that.

Now – I said there is an optional variant where you can specify a conic section – to do that use:

cylinder(h,r1|d1,r2|d2,center);

Where:

  • cylinder: The name of the function
  • h: the height of the conic section (in the z axis)
  • r1|d1: There are two options here, you can either use r1 = value, or d1 = value. r1 indicates you’re setting the radius of the bottom of the conic section. d1 indicates the bottom diameter (2r) of the conic section.
  • r2|d2: There are two options here, you can either use r2 = value, or d2 = value. r2 indicates you’re setting the radius of the top of the conic section. d2 indicates the top diameter (2r) of the conic section.
  • center: This is an optional parameter – if you set it to true, the conic section will spawn in the center of the XYZ origin. Otherwise it will be centered along the XY origin, but it’s bottom surface will be on the Z=0 surface.

So say I wanted to make the head on a flat head screw, where the top is 8mm, and the bottom is 5mm (diameters), and a height of 3mm

cylinder(3, d1 = 5, d2 = 8);

And that ends up looking like:

The Sphere

The last primitive shape we’re going to look at is the sphere. It’s pretty straight forward:

sphere(radius | d=diameter)

Where your only parameter is:

  • r|d: There are two options here, you can either use r = value, or d = value. r indicates you’re setting the radius of the sphere. d indicates the diameter (2r) of the sphere.

The spheres are always centered along the X, Y, and Z axis. So if I wanted to make a Ping Pong ball with a radius of 20mm, I would:

sphere(r = 20);

Which looks like:


Transformation Operators

Transformation operators allow you to change the pose, position, scale, and other properties of your mesh. There are several transformation operators – but we’re just going to look at the main 3. These operators work on meshes – and you’ll need to specify which mesh(es) you want to affect. The syntax is:

TransformationName (parameters) {
   Mesh1 (parameters);
   Mesh2 (parameters);
   MeshN (parameters);
}

UNLESS. Unless you’ve only got one mesh to operate on, in which case, you can cut out the curly braces:

TransformationName (parameters) 
   Mesh1 (parameters);

You can also nest transformations:

TransformationName1 (parameters){ 
   TransformationName2 (parameters){
      Mesh1 (parameters);
      Mesh2 (parameters);
      MeshN (parameters);
   }
}

Translation

Translation causes the selected mesh(s) to move along the X, Y and/or Z axis. The syntax is:

translate([x,y,z])

Where:

  • x: Movement along the X-axis
  • y: Movement along the Y-axis
  • z: Movement along the Z-axis

Note that these numbers can be positive, negative, and even contain fractional numbers (IE 3.14). Also, note that there is no semicolon following this line. So if I wanted to move our screw head up 30mm:

translate([0,0,30])
   cylinder(3, d1 = 5, d2 = 8); // Create the screw-top

Which results in:

So the mesh moves 0mm in the X, 0mm in the Y, and 30mm in the Z axis.

Rotation

Rotation allows you to modify the selected meshes rotation around the X, Y, and or Z axis – AKA the roll, pitch, and yaw. The syntax is:

rotate([x,y,z])

Where:

  • x: Rotation about the X-axis
  • y: Rotation about the Y-axis
  • z: Rotation about the Z-axis

So let’s take a rather large flat thin plane and rotate it on every axis:

rotate([20, 40, 60])
   cube([50, 50, 2]);

Which gives us:

There’s a special note to be made here – this rotation is always about the origin (0, 0, 0) – so this can cause some interesting problems if you are translating & rotating the same mesh (IE if you translate first, then rotate). If this is particularly a problem, you can look into setting an arbitrary rotation axis.

Scaling

Scaling allows you to stretch the specified mesh(s) in the X, Y, and or Z axis. The syntax is just like translate & rotate:

scale([x,y,z])

Where:

  • x: scale multiplier along the X-axis
  • y: scale multiplier along the Y-axis
  • z: scale multiplier along the Z-axis

So let’s make an ideal(ish) skipping stone:

scale([8, 6, 0.8])
   sphere(r = 10);

Honorable Mention – Colors

Just a quick note – sometimes (especially when there are many parts in your design) it may be wise to set the color of a mesh. There’s a couple ways to do it, but the easiest method for me to explain is by setting the hex color code:

color("#hexvalue")

So I can make my skipping stone gray with:

scale([8, 6, 0.8])
   color("#9a9b91")
      sphere(r = 10);

Boolean Operators

Boolean operators allow you to put meshes together or take cut them up. There are only 3: union, difference, and intersection, and we’re going to look at all of them.

Here’s a quick figure summarizing them:

Union OperatorDifference Operator (Right Cylinder from Left)Intersection Operator

The Union Operator

The union operator takes all of the selected meshes and groups them together as one complete mesh. The syntax is quite simple – and there are are no arguments:

union (){
   Mesh1 (parameters);
   Mesh2 (parameters);
   MeshN (parameters);
}

So in that above command, every mesh listed in the curly braces are added together, and now they all act as one bigger mesh. For example, let’s make a basic pin:

union (){
   sphere(d = 10); // Create the pushable part
   color("#9a9b91") // Make the next mesh grey (the pin)
      cylinder(25, d1 = 2, d2 = .001); // Create the sharp part
}

The result is:

The Difference Operator

The difference operator allows us to subtract one mesh from another. The syntax looks like:

difference (){
   Mesh1 (parameters);
   Mesh2 (parameters);
   MeshN (parameters);
}

But there’s a little bit more unseen nuance here that makes this function different from the union operator. Mesh1 is always positive (meaning that it will be shown), where as all of the following meshes 2 through N are negative (meaning they will show up as holes in Mesh1). But what if you want more than 1 positive mesh? Simply union multiple meshes together in the first slot of the Difference Operator, then logically it will act as one single mesh.

So let’s make a bowl:

difference (){
    sphere (d = 20); // Make a solid sphere
    sphere (d = 18); // Make the sphere hollow
    cylinder (d = 21, h = 11); // Cut off the top of the bowl
}

The Intersection Operator

The intersection operator displays the overlap of all of the meshes operated on by the function. To restate that – it leaves you with only the positive area that ALL of the meshes inside the curly braces have in common. The syntax is pretty straight forward:

intersection(){
   Mesh1 (parameters);
   Mesh2 (parameters);
   MeshN (parameters);
}

Let’s make a loaf of bread looking shape by stretching a sphere and taking it’s intersection with a cube.

intersection()
{
    sphere(d = 100);
    translate([0,0,50])
        cube([80, 30, 100], center = true);
}

And it looks like:


Mathematical Operators

Anywhere where you can put enter a number as a parameter in OpenSCAD, you can also use math symbols. The operators are about what you’d expect:

SyntaxNameResult
n + mAdditionThe sum of n & m
n - mSubtractionThe difference of m from n
n * mMultiplicationThe product of n & m
n / mDivisionThe quotient
n % mModuloThe remainder from an integer division
n ^ mExponentiationn is taken to the power m
n < mLess Than1 if n is less than m, otherwise 0
n <= mLess or Equal1 if n is less or equal to m, otherwise 0
b == cEqual1 if b is equal to c, otherwise 0
b != cNot Equal1 if b is not equal to c, otherwise 0
n >= mGreater or Equal1 if n is greater than or equal to m, otherwise 0
n > mGreater Than1 if n is greater than m, otherwise 0
b && cLogical And1 if and only if b and c are 1
b || cLogical Or1 if b or c is a 1
!bNegationif 1, turn to 0, otherwise turn to 1

The last 9 operators listed there are intended for boolean algebra – usually reserved for flow control statements (like if-thens, loops, etc). We’ll get to that in a few sections.

There are also several useful math functions like absolute value, sine, natural log, etc. that can come in handy – they’re all listed on the cheatsheet.


Parametric Design

Man this page is getting pretty long. Sorry there’s a lot of information here. Anyways – this section is important – it’s where we start to get into the real magic of OpenSCAD. We have the ability to create variables. For example – we could have a parametric (this means the model is determined by variables) shoe model that is based off of a shoe size variable. We could make that variable with:

shoeSize = 11;

Anywhere you can use a number as a parameter, you can also use a variable. So for example, let’s make a ring that depends on the diameter of the intended wearer’s finger:

fingerDiameter = 15;
bandThickness = 2;
bandHeight = 5;
union()
{
    difference(){
        cylinder(bandHeight, d = fingerDiameter + bandThickness * 2, center = true);
        // Cut out the hole of the ring
        cylinder(bandHeight, d = fingerDiameter, center = true);
    }
    // Just for fun, let's add a diamond
    translate([0, -(fingerDiameter / 2 + bandThickness), 0])
        rotate([90, 0, 0])
            cylinder(bandHeight, d1 = bandHeight / 2, d2 = bandHeight);
}

Let’s see how the ring looks for a few different diameters.

fingerDiameter = 10fingerDiameter = 15

Note that the ring isn’t simply “scaling up” since the width and the thickness of the band are staying constant. You can see that even if something isn’t going to be used in a formula, it can still be handy to have it referenced as a variable, because it makes all of your mesh dimensions much more understandable (the alternative is to just put in “magic numbers” that just work.

Flow Control

Flow control structures are implement ideas like if-then, for loops, etc. They can be really handy for furthering the power of OpenSCAD’s parametric nature. There’s a couple variations of these statements that you can read about on OpenSCAD’s wiki, but let’s just look at the most basic & common implementation.

If Statement

The “If statement” allows for us to check if a condition is true (or not true), and then act accordingly on it. The syntax looks like this:

if (Condition){
   // If the condition is true, the code in the curly braces is executed
}

We can also have an If-Then-Else structure:

if (Condition){
   // If the condition is true, the code in the curly braces is executed
}
else {
   // If the condition is false, the code in these curly braces is executed
}

And finally – you can through in else if statements:

if (Condition 1){
   // If the condition is true, the code in the curly braces is executed
}
else if (Condition 2){
   // If condition 1 is is false, and condition 2 is true, the code in these curly braces is executed
}
else {
   // If neither condition 1 or 2 is true, the code in these curly braces is executed.
}

So for example – let’s make a mesh that turns a cylinder red if it’s radius is above 10 mm (otherwise keep it the default color)

cylinderSize = 15; // Size of the cylinder
if (cylinderSize > 10) {
    color("#ec0b0b")
        cylinder(cylinderSize * 4, r = cylinderSize);
}
else {
    cylinder(cylinderSize * 4, r = cylinderSize);
}

Perhaps instead of me generating the figure – you should pull this code into your own OpenSCAD and play with it.

For Loop

For loops allow you iteratively add / modify meshes. They loop through and increment / decrement their index, which is a number that you can use within formulas, which is magic. The syntax looks like:

for (i = [start:step:end]){
   // Execute the code in here "1 + (start - end) / step" times
}

So in the syntax above, the first value of i would be “start”, then the next value of i would be “start” + “end” (or “-” if end is less than start). If you want the step to be 1, you can just omit this part:

for (i = [start:end]){
   // Execute the code in here "1 + (start - end)" times
}

And like all of the other structures, you can nest these (IE a for loop inside a for loop). Just make sure you chose a different index variable name.

Let’s make a bunch of cubes and have their heights be determined by a sine wave.

numCubesX = 50;
numCubesY = 50;
cubeSize = 3;
cubeSpace = 3;
amplitude = 5;
for (x = [0:numCubesX - 1]){
    for (y = [0:numCubesY - 1]){
        translate([x*(cubeSize + cubeSpace),y*(cubeSize + cubeSpace), amplitude * sin(x*y)])
            cube(cubeSize);
    }
}

Neat!


Modular Design

So suppose you have a part that you’re going to need to make over and over again. Instead of copying and pasting the code several times, it probably makes sense to turn it into a module.

A module is a defined mesh, that when invoked somewhere else in the code, will effectively copy that code where it was invoked. To make a module:

module name(List of Input Parameters){
   // Put code in here that utilizes Input Parameters
}

And if you want to utilize the module, just call it like this:

name(Parameter1, Parameter2, ParameterN);

So for example, let’s suppose I want to have a module for countersunk screw holes that I can place wherever I want. Here’s the whole script:

$fn = 50; // Special Variable that makes the holes smoother
ext = 0.01; // A Little extra

module screwHole (shaftLength){
    cylinder(3 + ext, d1 = 4, d2 = 8); // Create the screw-top
    translate([0,0,-shaftLength + ext])
        cylinder(shaftLength + ext, d = 4); // Create the shaft
}

// Let's make a plate to place some of the holes in.
plateWidth = 60; // Width of the plate
plateLength = 40; // Length of the plate
plateHeight = 7; // Height of the plate

// Create the plate and inset holes in corners
difference(){
    cube([plateWidth, plateLength, plateHeight]);
    // Subtract out the holes
    translate ([5, 5, plateHeight - 3])
        screwHole (10);
    translate ([plateWidth - 5, 5, plateHeight - 3])
        screwHole (10);
    translate ([5, plateLength - 5, plateHeight - 3])
        screwHole (10);
    translate ([plateWidth - 5, plateLength - 5, plateHeight - 3])
        screwHole (10);
}

Check it out – that translate statement is used with every hole – why not just bring that into our module so we don’t have to type it every time? This will result in overall cleaner code.

$fn = 50; // Special Variable that makes the holes smoother
ext = 0.01; // A Little extra

module screwHole (shaftLength, X, Y, Z){
    translate ([X,Y,Z]){
        cylinder(3 + ext, d1 = 4, d2 = 8); // Create the screw-top
        translate([0,0,-shaftLength + ext])
            cylinder(shaftLength + ext, d = 4); // Create the shaft
    }
}

// Let's make a plate to place some of the holes in.
plateWidth = 60; // Width of the plate
plateLength = 40; // Length of the plate
plateHeight = 7; // Height of the plate

// Create the plate and inset holes in corners
difference(){
    cube([plateWidth, plateLength, plateHeight]);
    // Subtract out the holes
    screwHole (10, 5, 5, plateHeight - 3);
    screwHole (10, plateWidth - 5, 5, plateHeight - 3);
    screwHole (10, 5, plateLength - 5, plateHeight - 3);
    screwHole (10, plateWidth - 5, plateLength - 5, plateHeight - 3);
}

It is also very wise to take each part of a multi-part assembly, and encasing them in their own module with a descriptive part name.


Variable Scope & Special Variables

I’m going to talk about special variables first because I just used some and you’re probably wondering what they are. There’s 2 special variables I use frequently:

  • $fn – this specifies the number of fragments that make up a round surface. The bigger this number, the less “blocky” a round surface will look. However this can bog your system down if you set it too high and have too many round surfaces.
  • ext – this isn’t a special variable per se, but I use it like one. It’s a value I use to add a little extra height to something – this prevents conflicting mesh issues and edge cases (I’ll show you what I mean in one of the example videos)

Now – if you declare a variable outside of a module, the variable can also be used inside of the module. However if you declare it again inside of a module, it will overwrite the value. So normally I have global variables at the top of my script, and then module specific variables at the top of my module.


Examples

Print Friendly, PDF & Email