Pronounced “open-ess-cad,” according to the OpenSCAD web site.
OpenSCAD is a constructive geometry 3D modeling program. It is less popular than some other 3D modeling tools such as FreeCAD, SketchUp, or Blender, but has some advantages for creating 3D part designs:
-
Parts are created by writing a script, not with the mouse. This makes some things easier for part design, but other things harder.
-
Can create libraries of part designs for others to use. Or use existing libraries such as MCAD.
-
Parts can be parameterized to make then easy to customize.
-
It’s harder, in general, to create a non-solid model than with some other 3D modeling tools—important when 3D printing.
-
Good for designing CAD models, bad for artistic modeling.
The latest release of OpenSCAD, as of this writing, is 2015.03-1. Download OpenSCAD from the OpenSCAD web site, http://www.openscad.org/downloads.html. There are installers for Windows, a DMG for OS X, and instructions for various Linux distributions.
In the editor window, enter this (very small) script and press F5 to render the object in the view pane. You can instead press the preview button , which is the same as pressing F5, or use the menu option Design > Preview. After you have saved the file once, updating the preview is automatic as soon as the file is saved.
cube();
You should then see a small cube shown in the view:
The cube is 1x1x1, which is quite small. Zoom the view using the zoom button
, or press the zoom all button
to adjust the view to match the size and position of your model. You can also use the mouse wheel or a zoom gesture on your touchpad. All of the commands that affect the view are in the View menu as well.
Clicking and dragging in the view rotates the axes around so you can see all sides of your model. By default the origin is in the center of the view, and the view rotates around the origin. You can drag the view laterally without rotation by Ctrl-click and drag. After that the view rotates around the new center of the view, rather than the origin. There are also buttons below the view panel for choosing particular eye positions, such as top, right, and so on.
The use of cube();
above demonstrates 2 things about the structure of OpenSCAD scripts.
-
cube()
is an example of a predefined OpenSCAD module. In this case it is what is called an object module, because it creates 3D objects. The parentheses are required when invoking a module. -
The whole line—with the semicolon—is an example of an OpenSCAD action. (We would probably call it a statement in another language.) OpenSCAD scripts consist of a number of actions. Each action may span multiple lines but must end in a semicolon (or sometimes be wrapped in braces:
{
and}
).
We obviously don’t want cubes of only one size. We can also create cuboids where the sides have different lengths.
cube(size=[10, 5, 8]);
This produces a cuboid of size 10 units on the X axis, 5 units on the Y axis, and 8 units on the Z axis.
OpenSCAD is usually used to create STL files for 3D printing. STL files do not have a way to indicate a physical measurement to correspond to a unit of 1 in the STL file. Instead, the 3D printing or slicing program will have an option to specify whether to interpret the STL units as millimeters, tenths of inches, or some other measure.
I usually design parts using millimeters, a practice that appears to be common among others using OpenSCAD, but you can make your own decision how to handle units of measure.
The coordinate axes follow the right-hand rule where the arrangement of your thumb and first two fingers on your right hand match the X, Y, and Z axes, respectively.
The addition of size=[10, 5, 8]
is an example of passing a parameter to a module in order to modify what object it creates. The cube()
module takes these parameters:
size
-
Either a single number or a vector of three numbers giving the X, Y, and Z dimensions. If you supply a single number, it will be used for all three dimensions.
center
-
If
true
, the center point of the cuboid will be at the origin. Iffalse
or omitted, a corner of the cuboid will be at the origin.
Creating a cuboid with and without centering:
Parameters in OpenSCAD work a little differently than parameters in some other languages.
-
They may be omitted if there is a default value. For
cube()
, for example,center
defaults tofalse
andsize
defaults to1
. -
They have names which may be provided. If they are not provided, then the parameters need to be in the right order.
cube()
expectssize
first, andcenter
second, socube(size=[10, 5, 8])
andcube([10, 5, 8])
are equivalent. -
If you use the parameter names, you can specify the parameters in any order.
-
Some parameters may take values of different types.
cube()
allowssize
to be a number or a vector, for example. -
You can specify parameters the module does not expect. These will be silently ignored. (A bad design decision, IMHO, but probably caused by the handling of special variables--see below.)
Based on these characteristics of parameters, all of these actions are equivalent ways to create a 10x5x8 cuboid:
cube(size=[10, 5, 8]); cube([10, 5, 8]); cube([10, 5, 8], false); cube(center=false, size=[10, 5, 8]); cube(size=[10, 5, 8], false); cube([10, 5, 8], center=false);
Note that the only way to specify center
first is to include the names of both parameters.
In the examples above, we see three different types of values that can be specified as parameters.
- numbers
-
In OpenSCAD these are always floating-point numbers. IEEE784 representation is used, giving about 17 digits of precision.
- vectors
-
Vectors are sequences of values enclosed in the square brackets
[
and]
, and separated by commas. Vectors can hold items of any type, including other vectors:[[1, 2, 3], [4, 5, 6]]
- boolean values
-
true
andfalse
are built-in boolean constants. There are also operators which give boolean results.
In addition, OpenSCAD supports string values enclosed in double quotes.
"hello"
Some of the standard escape sequences are valid, including \"
, and also the ability to specify Unicode code points using hexadecimal, such as \u201D
.
"this is a string with a quote \" in the middle"
Now that we have a cuboid that has differing edge lengths, we can tell the difference between a view from a different side. There are six standard viewpoints to look down each axis in either direction, right, top, bottom, left, front, and back. These are all available in the View menu, or you can use the buttons below the view pane:
Clicking and dragging the mouse rotates the view about the point in the center of the view, by default the origin. You can instead drag the view left or right by Ctrl-click and drag, or by dragging with the right mouse button. If you want to center the axes in the view again, press the “reset view” button
.
By default the view shows a perspective projection. That is, a 2-dimensional view of the 3D scene in which objects in the view seem smaller as they recede away. To see this, zoom the view so that the cube takes up much of the area of the pane. Notice that the edges of the cube converge toward a vanishing point.
-
Create a cuboid that is 20 units long in the X direction, 3 in the Y direction, and 6 in the Z direction.
-
Modify your use of the
cube()
module to center the cuboid around the origin. -
Rotate the view around using the mouse so that the narrow end of the cuboid is pointing toward you, but you can still see the top face of the object.
-
Zoom the view so you can see that the edges of the cuboid parallel to the X axis converge to a vanishing point.
-
Change the view to an orthogonal projection so that the edges now are parallel and don’t converge to a vanishing point.
-
Use the buttons to select each of the 6 standard views.
-
Use Ctrl-click and drag to move the view around laterally, without rotation.
-
Reset the view back to a diagonal viewpoint.
To place objects somewhere other than the origin, you use the
translate()
module. For example, this code offsets a cuboid so that it sits on the X-Y plane, but 10 units along the X axis and 5 along the Y axis.
translate(v=[10, 5, 0]) { cube(size=[20, 3, 6]); }
The translate()
module is our first example of what OpenSCAD calls an operator module, one which does not produce 3D objects, but modifies how other objects are rendered. translate()
takes a single argument v
which is a vector of the distances along the X, Y, and Z axes to offset the objects it is modifying. The objects to be offset are placed inside braces, {
and }
. If there is only one module to be operated on, the braces can be omitted. This script is equivalent to the one above:
translate([10, 5, 0]) cube(size=[20, 3, 6]);
In this case the parameter name v
has also been omitted. Some will
indent the second line:
translate([10, 5, 0]) cube(size=[20, 3, 6]);
I tend not to indent unless using braces to wrap the target objects, since multiple operators may be combined, and I find the resulting indentation makes the script harder to read.
OpenSCAD is a language similar in syntax to C and Java. For that reason, using a writing style similar to what you might use in C or Java makes sense. The rest of the examples herein will use these rules. But they are my rules, not necessarily what you will see in other OpenSCAD scripts.
-
Opening braces will be on the same line as the operator they follow.
-
If braces are used to surround the operands of an operator module, the contents inside the braces will be indented. The default indent amount in the OpenSCAD editor is four spaces, but you can change this in the preferences dialog. I find two spaces is sufficient.
-
If braces are not used to surround the operand of an operator module, the operand will use the same level of indentation as the operator.
-
The parameter name will be omitted if a module takes a single parameter and included otherwise.
To draw more than one object, just put the actions one after anohter. For example, this draws a wall with an entranceway.
cube(size=[8, 2, 6]); translate([12, 0, 0]) cube(size=[8, 2, 6]); translate([18, 0, 0]) cube(size=[2, 20, 6]); translate([0, 18, 0]) cube(size=[20, 2, 6]); cube(size=[2, 20, 6]);
The OpenSCAD documentation is viewable in a web browser. There are links in the Help menu to launch a browser window to the help pages. The most important of these links are:
- Help > Cheat Sheet
-
This launches a quick-reference page of all the modules and functions—we haven’t talked about those yet—available. The documentation on each is usually quite good. All the parameters are explained, usually with examples.
- Help > Documentation
-
This launches a menu of available documentation, including the OpenSCAD language reference. The language reference has information on some topics, such as data types, which are not listed on the cheat sheet.
-
Draw an arch using three cuboids, like the image below. Make the two uprights and the crossbar have the same cross-section, and the overhang of the crossbar the same on each end. (The exact dimensions aren’t important.)
-
Manipulate the view to see each side, to make sure the arch is symmetric.
-
Modify the crossbar so there is no overhang past the upright posts.
-
Move the uprights closer together. Make sure the crossbar stays flush with the uprights at each end.
-
Use the OpenSCAD cheat sheet to look at the documentation on the
scale()
operator module. Usescale()
to double the size of your arch. Hint: Use braces,{
and}
, to group together what you want to scale.
Different objects in your scripts can blend together in the 3D view, making it more difficult to figure out what is wrong if your model isn’t exactly right. There are two prefix characters you can prepend to an object to cause it to be drawn specially, enabling you to see better how the parts fit together.
#
: Highlight—draw an object in a translucent red so it stands out.
%
: Transparent—draw an object in a translucent gray so you can see through it. This also omits the object from the rendered STL file.
An example: here is a solution to the arch problem drawing one upright transparent and the other highlighted.
%translate([5, 0, 0]) cube(size=[5, 5, 15]); #translate([20, 0, 0]) cube(size=[5, 5, 15]); translate([0, 0, 15]) cube(size=[30, 5, 5]);
Narrowing the arch or removing the overhang required modifying multiple numbers in the script. OpenSCAD allows you to create variables to avoid hard-coding numbers.
Variables are assigned anywhere in your script. Variable names are similar to C++ or Java identifiers. As an example, here is a small script that uses a variable.
cubeSize = [10, 3, 6]; cube(size=cubeSize, center=true);
The variable cubeSize
is defined in an action of the form
variableName =
value;
. Variables can take on values of any
OpenSCAD data type. Here, the cubeSize
variable takes on a vector
value.
There are some differences between OpenSCAD variables and those you may be familiar with in other languages.
-
Variables may only be assigned once. That is, they are essentially constants. The last value assigned in the script takes precedence. (Exceptions: New lexical scopes allow redefinition, and command-line definitions can take precedence over script definitions.)
-
Variables are untyped. Instead, they take on the type of the value assigned to them.
You may define as many variables as you like. Variables may be used in expressions to define parameter values.
Note
|
Because variables cannot be redefined, you will never see x =
x+1 in an OpenSCAD script.
|
You can also add comments to a script using either of the C++ comment
styles: //
introduces a single-line comment, while /*
and */
bracket either single-line or multi-line comments. There are also
commands in the Edit menu to comment or uncomment selected sections in
your script.
Variables and values can be combined in arithmetic expressions to form new values. The expression syntax is very similar to C++, Java, and other languages.
As an example, here is a sample solution to the arch problem that uses four variables.
// Cross-sectional size of the uprights and crossbar. blockWidth = 5; // Width between the two uprights. uprightSeparation = 10; // Amount the crossbar extends beyond the uprights. crossbarOverhang = 3; // Height of the uprights. The bottom of the crossbar is at this height. uprightHeight = 15; translate([crossbarOverhang, 0, 0]) cube(size=[blockWidth, blockWidth, uprightHeight]); translate([crossbarOverhang + blockWidth + uprightSeparation, 0, 0]) cube(size=[blockWidth, blockWidth, uprightHeight]); translate([0, 0, uprightHeight]) cube(size=[2*crossbarOverhang + 2*blockWidth + uprightSeparation, blockWidth, blockWidth]);
Solving problem 4 in exercise 2 requires eliminating the overlap and reducing the upright separation. This can be effected by modifying two variable definitions without touching the rest of the script. Or, you can run OpenSCAD from the command line, specify new values for these variables, and render an STL file, all in one step.
uprightSeparation = 5; crossbarOverhang = 0;
Creating only rectilinear objects is a little boring. OpenSCAD allows creation of more object types and more operators to modify their rendering.
The rotate()
operator performs rotations on its target
objects. There are several ways to perform rotations.
rotate(a=angle)
-
Rotates a given amount, in degrees, around the Z axis.
rotate(a=[xAngle, yAngle, zAngle])
-
Rotates a given amount, in degrees, around the X, Y, and Z axes, in turn.
rotate(a=angle, v=[x, y, z])
-
Rotates a given amount, in degrees, around an arbitrary vector.
All rotation amounts are in degrees and follow a right-hand rotation rule: if you point your right thumb toward the positive direction of the axis or vector around which you want to rotate, your fingers curl in the direction of positive rotation. Negative degrees rotate in the opposite direction.
The most common operation is to rotate around a single axis. Here is an oblong cuboid in its original position and rotated 45 degrees around each axis.
separation = 20; cubeSize = [10, 5, 2]; cube(cubeSize); translate([separation, 0, 0]) rotate(a=[45, 0, 0]) cube(cubeSize); translate([0, separation, 0]) rotate(a=[0, 45, 0]) cube(cubeSize); translate([0, 0, separation]) rotate(a=[0, 0, 45]) cube(cubeSize);
This example shows two operators being applied, rotation and translation. They are applied last to first--that is, here the rotation is applied first, then the translation. You will get different results if you reverse the order, as this example shows.
Rotation first | Translation first |
---|---|
|
|
You will probably seldom use the option to rotate around an arbitrary vector. One example of when it might be useful is rotating a cube around an axis through corner points, like this. (The view has been rotated to make it easier to see the result.)
rotate(a=60, v=[1, 1, 1]) cube(size=10);
Drawing cuboids is getting boring, so let’s learn some more objects.
sphere(r=radius)
-
Creates a sphere of a given radius, centered around the origin.
sphere(d=diameter)
-
Creates a sphere if a given diameter, centered around the origin.
cylinder(r=radius, h=height, center=true|false)
-
Creates a cylinder of given radius and height, either sitting on the X-Y plane or centered around the origin. In either case the height is in the Z direction.
cylinder(d=diameter, h=height, center=true|false)
-
Creates a cylinder of given diameter and height.
cylinder(r1=radius1, r2=radius2, h=height, center=true|false)
-
Creates a conical object that has differing top and bottom radii. This can be used to create cones or truncated cones.
cylinder(d1=diameter1, d2=diameter2, h=height, center=true|false)
-
Creates a conical object that has differing top and bottom diameters. This can be used to create cones or truncated cones.
The example below creates a dumbell shape along the X axis.
sphereSize = 6; barRadius = 2; barLength = 25; sphere(r=sphereSize); rotate([0, 90, 0]) cylinder(r=barRadius, h=barLength); translate([barLength, 0, 0]) sphere(r=sphereSize);
You can see in the example above that the cylinder isn’t very “round”--it only has seven sides! And the spheres have obvious facets. OpenSCAD does not really render curves. Instead, it creates triangular faces which approximate the curved surface. If you want more smoothness you need to modify how OpenSCAD chooses the number of faces to generate. There are three variables controlling the smoothness of round surfaces.
$fs
-
The maximum length of an edge.
$fa
-
The maximum angle spanned by a single face.
$fn
-
The number of faces to use around a curve.
You never use all three. Instead, you should either set both $fs
and
$fa
(preferred) or use $fn
. As well, $fs
and $fa
are used
together by OpenSCAD, taking the larger of the two values for each
face. Let’s say we want the faces approximating our curved surfaces to
take no more than 8 degrees of arc, or .5 units of length, whichever
is larger. Just add these two lines to the top of the script, and the
result is much smoother.
$fs = .5; $fa = 8;
Using very small values of $fs
and $fa
can make rendering much
slower, especially when creating STL files, so you should match them
to the precision needed in your final printing. The settings above
change the STL rendering on
my machine from almost instantaneous to 4 CPU-seconds. You may have to print
a sample or two before you figure out the right settings.
You can also specify the variables on single objects instead. The two cylinders created by this script use faces that span 10 and 20 degrees of arc, respectively.
cylinder(r=5, h=10, $fa=10, $fs=.5); translate([15, 0, 0]) cylinder(r=5, h=10, $fa=20, $fs=.5);
The other variable, $fn
, is most useful when you want to fix the
number of faces around a curve. For example, the following script
generates a hexagonal prism, not a cylinder.
cylinder(r=5, h=10, $fn=6);
For most people, the rendering in OpenSCAD is not the end goal. Instead, they want to take the model and print it on a 3D printer. For that purpose, you have to render the model to a mesh of triangular faces, and then export that rendering as STL.
First, press the render button, , or press F6. If you have a model with
curves, and you are using small values for
$fs
and $fa
, this can
take some time. After the rendering is complete, you can export to STL
by using the STL export button,
,
or by selecting File > Export > Export as STL… in the menu.
-
Write a script to create a (somewhat crude) chess pawn something like this. The exact dimensions aren’t that important. Use variables to control the various dimensions rather than hard-coding numbers.
-
Modify the values of
$fs
and$fa
to make the model smoother. Render the model by pressing the render button or F6. Notice that small values of$fs
and$fa
can make rendering very slow. -
Choose values for
$fs
and$fa
that you think are appropriate for printing on a 3D printer. (Make your best guess.) Render the model and then export to an STL file. Look at the file in a text editor, if you know how. It should start something like this:solid OpenSCAD_Model facet normal 0.996195 0.0871558 0 outer loop vertex 12.5 0 5 vertex 12.3101 2.1706 0 vertex 12.3101 2.1706 5 endloop endfacet ...
-
How would you change the conical shaft of the pawn into a tapered octagonal shaft?
-
Modify the variables used in the model to make the base and collar wider while making the shaft narrower. Did you do a good job parameterizing your model to make it easy to customize?
So far we have built up models by combining primitive objects and operators. We have, then, created a union of multiple objects. Union is an example of a boolean operator. OpenSCAD also supports two other boolean operations, intersection and difference. The last of these, difference, is probably the most important, because it lets you create holes in other objects.
Script | Result |
---|---|
union() { cube(size=[10, 10, 3], center=true); cylinder(r=2, h=12, center=true, $fs=.5); } |
|
intersection() { cube(size=[10, 10, 3], center=true); cylinder(r=2, h=12, center=true, $fs=.5); } |
|
difference() { cube(size=[10, 10, 3], center=true); cylinder(r=2, h=12, center=true, $fs=.5); } |
Union and intersection are straightforward to use and won’t cause problems when rendering. Difference, however, can cause problems in the model that mess up the preview, or even mess up the STL file. The script below shows the problem.
difference() { cylinder(r=5, h=5); translate([0, -8, 0]) cube(size=[8, 8, 5]); }
The script tries to cut a quarter out of a cylinder. It mostly looks
OK, but there is a strange artifact at the top and bottom of the
cut-out quarter. Let’s highlight the cuboid using #
and look at the
result edge-on.
The cylinder and cuboid are exactly the same height. The problem is that the cuboid abuts the cylinder at the top and bottom faces. The difference operator cuts a volume out of an object, but doesn’t cut faces out. This causes only the top and bottom faces of the cylinder to remain, but with zero volume. This actually renders to STL correctly, but the preview is messed up.
To correct the problem, extend the cube slightly—or a lot!--above and below the cylinder. Here is is edge-on with the cuboid highlighted, and the resulting preview without highlighting.
So, to avoid problems, always cut out a larger chunk than you need,
to avoid having the objects involved in the difference()
share
faces.
One approach to avoid the problem is to make any holes extend very
high and low. For example, heres a script that cuts some holes into a
rectangular base. There is a variable maxModelSize
that represents a
limit on how big the model could possibly be. Holes extend that much
below and above the X-Y plane. The second hole is
highlighted to show that it extends a long way above and below the X-Y
plane.
$fs = .5; $fa = 10; maxModelSize = 50; difference() { cube(size=[20, 15, 3]); translate([3, 3, 0]) cylinder(r=1, h=2*maxModelSize, center=true); #translate([10, 10, 0]) cylinder(r=1, h=2*maxModelSize, center=true); translate([17, 7, 0]) cylinder(r=1, h=2*maxModelSize, center=true); }
I want to create a model of a wrench, something like this:
To save some time, the model will be a little simpler, nonadjustable, with a rectangular handle rather than tapering, and a circular head. Let’s create the model in several steps.
We can start with a simple rectangle.
handleLength = 45; handleWidth = 10; handleHeight = 3; translate([0, -handleWidth/2, 0]) cube(size=[handleLength, handleWidth, handleHeight]);
The far end should be rounded, though, so we’ll shorten the rectangle and add a cylinder.
translate([0, -handleWidth/2, 0]) cube(size=[handleLength - handleWidth/2, handleWidth, handleHeight]); translate([handleLength - handleWidth/2, 0, 0]) cylinder(d=handleWidth, h=handleHeight);
I’ve highlighted the cylinder to show how the objects fit together.
And we need a hole in the end, which I’ve highlighted below.
maxModelHeight = 10; handleHoleDiameter = handleWidth - 4; difference() { union() { translate([0, -handleWidth/2, 0]) cube(size=[handleLength - handleWidth/2, handleWidth, handleHeight]); translate([handleLength - handleWidth/2, 0, 0]) cylinder(d=handleWidth, h=handleHeight); } #translate([handleLength - handleHoleDiameter/2 - 2, 0, 0]) cylinder(d=handleHoleDiameter, h=2*maxModelHeight, center=true); }
The head of the wrench will be a cylinder centered at the origin.
headDiameter = 2*handleWidth; cylinder(d=headDiameter, h=handleHeight);
We need a slot in the head. The slot should be roughly rectangular, but with a rounded end. Here is the code for the slot, and an image of how the slot fits with the head.
slotWidth = 10; slotLength = headDiameter * 3/4; slotRoundingRadius = 8; translate([-headDiameter/6, -slotWidth/2, -maxModelHeight]) intersection() { cube(size=[headDiameter, slotWidth, 2*maxModelHeight]); translate([slotRoundingRadius, slotWidth/2, 0]) cylinder(r=slotRoundingRadius, h=2*maxModelHeight); }
Now we need to rotate the slot around to complete the wrench.
// A wrench model. $fs = .5; $fa = 10; // A size used for making holes. Any part of the model that gets // a hole should lie within the two planes z = +/- maxModelHeight. maxModelHeight = 10; // Dimensions of the wrench handle and hole at the end. handleLength = 45; handleWidth = 10; handleHeight = 3; handleHoleDiameter = handleWidth - 4; // Dimensions of the head end and the slot. headDiameter = 2*handleWidth; slotWidth = 10; slotLength = headDiameter * 3/4; slotRoundingRadius = 8; slotRotation = 30; difference() { // The handle and head, minus the slot and hole. union() { cylinder(d=headDiameter, h=handleHeight); translate([0, -handleWidth/2, 0]) cube(size=[handleLength - handleWidth/2, handleWidth, handleHeight]); translate([handleLength - handleWidth/2, 0, 0]) cylinder(d=handleWidth, h=handleHeight); } // The hole at the end of the handle. translate([handleLength - handleHoleDiameter/2 - 2, 0, 0]) cylinder(d=handleHoleDiameter, h=2*maxModelHeight, center=true); // The slot in the head. rotate(180 + slotRotation) translate([-headDiameter/6, -slotWidth/2, -maxModelHeight]) intersection() { cube(size=[headDiameter, slotWidth, 2*maxModelHeight]); translate([slotRoundingRadius, slotWidth/2, 0]) cylinder(r=slotRoundingRadius, h=2*maxModelHeight); } }
You are to create a model of a mount for the Raspberry Pi camera.
The dimensions of the camera are as follows.
You can see an example of a commercial camera mount below.
We can make it more rectilinear, though, and without a top covering piece.
This is a difficult problem that requires you to use cuboids and cylinders, boolean operations, and rotations. To avoid getting stuck, concentrate on one part of the mount at a time. For example, if you don’t know how to create the whole mount, can you create the base? Or can you create the main part of the mount with just the square hole for the camera lens?
More hints: model both the base and the upright portions as horizontal in the X-Y plane, and then rotate the base as a last step. Make sure your holes are of sufficient depth to cut them out completely.