Introduction: OpenSCAD: Introduction and Notes

About: Hi, I'm Craig. I live in the UK.

This started out as a way of me putting some notes online for the kind of things I found myself regularly searching for, mainly for personal use. It's grown to a bit more of an introduction to OpenSCAD than I was planning so I thought I'd publish here and I hope it is of use to anyone interested in the area. If you have questions I'll try and answer them in the comments bellow.


Intro

OpenSCAD is a free opensource CAD program, it runs on Windows, Mac and Linux.

Like many CAD packages an easy way to start is to build your object from 3D 'primitive' shapes, e.g. cubes, cylinders and spheres. A little different from many other packages it's not directly interactive, you can't grab a point or edge and drag it out. Instead you describe thing programmatically and OpenSCAD interprets your code. This may take a little getting used to but it means if you want an obscure feature you can just write it!

First two go-to references are:

Instructables already has several guides including some some useful specifics such as:

If you find (or have written) others please message me and I'll add to the list.

Step 1: Some Basics

A few quick basics...

  • Primitives
  • Comments
  • Modules & functions


Primitives

The three basic 3D primitives are:

  • Cube
  • Cylinder
  • Sphere

Sizes and co-ordinates are given ([X, Y, Z]) and each instruction ends with a semi-colon.

F5 shows a preview of your object and F6 will do a full render, I only do a full render when I'm exporting for 3D printing or laser cutting, it can take a little while based on the complexity of you object and the power of your machine.

A full description (and useful reference for all things OpenSCAD) can be found at: OpenSCAD user manual the section on primitives is at: Primitives.


Sphere

The simplest of the primitive shapes, pass it a size, either radius or diameter and press F5 to preview.

- Note: On a mac air it needs fn+F5 to preview.

sphere(d=10);

This preview doesn't look very smooth, we can set the number of faces on an object with '$fn=' inside the brackets. If you place a '$fn' at the start of a project all objects after will aim for that number of faces unless specified otherwise.

sphere(d=10, $fn=200);

You can set this as high or as low as you want but be aware that calculating millions of faces will take much longer!

Cube

A cube can be described with:

cube(10);

This would be a cube, 10mm on each side. Pressing F5 will show a preview of the cube.

Alternatively you could use:

cube([10,20,30]);

This would make a cuboid with X=10mm, Y= 20mm and Z=30mm.

You will notice one corner is at 0,0,0. We can center any primitive with:

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

In this case the center of the cube is now at 0,0,0.


Cylinder

A cylinder can be described with using 'r' for the radius or, 'd' for the diameter. you also need to give it a height 'h'.

cylinder(d = 10, h  = 10);

Again, this preview doesn't look very smooth, we can set the number of faces on an object with $fn= inside the brackets.

cylinder(d = 10, h  = 10, $fn = 250);

You can also set it lower than the default, for example:

cylinder(d = 10, h  = 10, $fn = 3);

This gives a triangular prism. It's maybe not quite the size you want but we can sort that kind of thing out in the modules section.

Cylinder - cone

You can give a cylinder two diameters to make a cone, d1 is the lower and d2 the upper.

cylinder (d1=40, d2=20, h=20);


Comments

It's always handy to leave a few hints as to what you were aiming to do, OpenSCAD allows commenting in the code with either '//' for one line or block commenting with '/* ...... */'

// Single line commented out
/* Large block

removed from preview

and render */


Modules & functions

A handy way to organise code, make parts and reuse code is to use modules and functions, OpenSCADs name for reusable blocks of code. Enclosed in curly brackets and end with a semi colon.

I use modules to make up parts of a large objects, this means once I'm happy with a final assembly I can the use the module for printing or laser cutting etc.

module test_part(){


};

When you want to use the module call it with:

test_part();

A module will make a part (or sub-part etc.) but doesn't return anything. OpenSCAD can also do functions, these will return a value, e.g doing a calculation.

function sine_vec_func (range)= [for (x = range) [ x, sin(x)]];

This world generate a list of vector point ([x,y}), we could check the out put with an echo:

sin_vector = sine_vec_func([0:180]);
echo (sin_vector);

So I assign the output of the function to a variable called sin_vector, by calling it and passing a range. Then I echo (print to terminal) the variable to check it's what I think it should be.

Step 2: Moving Things Around

With a few basic shapes we now want to position and manipulate them. The main functions for this are:

  • Translate
  • Rotate
  • Scale


Translate

This moves the object to the co-ordinates X, Y, Z.

translate([10,20,0]) sphere(d=10);

So the center of the sphere is off set from 0,0,0 by 10 in the x axis, 20 in the y axis and 0 in the z axis.


Rotate

This rotates about an axis, X, Y, Z.

rotate([0,20,45]) cube([10,20,30);


Scale

This will scale your object along the X,Y or Z axis. This is a relative scaling, so '1' is the original size, and e.g. '2' double and '0.5' would be half the original size.

scale([1,1,2]) sphere(d=10);

Step 3: Difference

This is possibly the most important function! It allows you to remove one shape from another.

An example would be adding a hole for a bolt:

difference(){
cube([30,30,3]);
translate([15,15,-0.1]) cylinder(d=3.2, h=3.2);
};

The syntax is that the first shape has all following shapes removed, in the above case a 30x30x3mm plate has a 3.2mm hole through the middle. To keep things neat, the object being remove should sit proud of the surfaces it is cutting though, in the above case I used a cylinder of 3.2mm to go though a 3mm plate, offsetting by 0.1mm so that the cylinder removed is 0.1mm above and bellow the plate. The second picture has a 3mm cylinder removed from a 3mm plate, as you can see there isn't a hole. I think it's left an infinitely thin surface because the command is unclear.

It's also easy to do a difference with multiple shapes:

difference(){
cube([30,30,3]);
translate([15,15,-0.1]) cylinder(d=3.2, h=3.2);
translate([5,5,-0.1]) cube([2,3,3.2]);
translate([23,22,-0.1]) cube([2,3,3.2]);
};

If you are having problems getting an object in the correct place then '#' will highlight it, this will make an object being differenced appear as an opaque red making easier to see where it is:

difference(){
cube([30,30,3]);
translate([15,15,-0.1]) cylinder(d=3.2, h=3.2);
translate([5,5,-0.1]) cube([2,3,3.2]);
#translate([23,22,2]) cube([2,3,3.2]);
};

Step 4: Importing Models

You may want to use an existing model such as an .stl or .dxf:

import("path/to.yourfile.stl", convexity = 3);

You can also drag your file into the code window to generate this.

The you can apply any functions you need, such as difference, scale and intersection.

Step 5: Modules I Use...

The more I use OpenSCAD the more I reused solutions from previous designs, my original aim for these notes was to share some of these modules, so here is a collection of things I've found or written which now make drawing quicker and easier:



Copy Mirror

This is from the OpenSCAD forum from Greg Frost, it copies and mirrors the part follows the function.

module copy_mirror(vec=[0,1,0]){
children();
mirror(vec) children();
};

This is one of my go-to functions for anything symmetrical. You can change the axis around which the copy is mirrored by changing the 'vec', leaving it empty will mirror along the Y axis. A quick use example would if I have four mount points arranged around a center I can draw one, mirror it across the Y axis then mirror this pair across the X axis.



Copy translate

Based on Greg's module this will clone the original part and move it to the desired vector.

module copy_translate(vec=[0,0,0]){
children(); // This maintains the original part
translate(vec) children();
};

So to copy a part an move it 50mm along the X axis you would use:

copy_translate([50,0,0]) cube(20);

This is quick and useful for cloning once or twice, if I was doing more I'd probably use a for loop.



For loops

This idea of using a module part to clone a part can be built on with a for loop to repeat a part many times:

for (i=[0:20:100]){
translate([i,0,0]) part();
};

So this would make 'part()' and place it at 0, 20, 40, 60, 80 and 100 along the X axis.

The 'for loop' outputs the value 'i', this is each value in the range 0:100 with a step size of 20. The value of 'i' is then used as the x value of a translate. The value of 'i' could be used for anything, scale, rotate, translate. It can also be changed e.g. multiplied by 10 ( i*10).



Rounded boxes

This takes a little longer to render than a plain cube but looks a bit nicer is some places.

module rounded_edge_box(x_dim, y_dim, z_dim, radius){
hull(){
//Bottom
translate([radius,radius,radius]) sphere(radius);
translate([x_dim-radius,radius,radius]) sphere(radius);
translate([radius,y_dim-2*radius,radius]) sphere(radius);
translate([x_dim-radius,y_dim-2*radius,radius]) sphere(radius);

//Top
translate([radius,radius,z_dim-radius]) sphere(radius);
translate([x_dim-radius,radius,z_dim-radius]) sphere(radius);
translate([radius,y_dim-2*radius,z_dim-radius]) sphere(radius);
translate([x_dim-radius,y_dim-2*radius,z_dim-radius]) sphere(radius);
};
};

There was an excellent video by MakersMuse on the pros and cons of chamfer and filleted edges for 3D printing.



Ruler

I like to pull in an object to show scale of a design, one of my preferred items is a 30cm ruler (or 12" if you are that way inclined). I have a ruler of these dimensions to hand in the real world and I can use it to double check I've got critical dimensions right.

module ruler(ruler_length = 300, ruler_thickness = 2, ruler_height = 20){

translate([0,0,ruler_thickness])rotate([-90,0,0])difference(){
union(){
cube([ruler_length, ruler_thickness, ruler_height]);
translate([ruler_length,ruler_thickness,ruler_height/2])rotate([90,0,0]) linear_extrude(height = ruler_thickness)circle(r=ruler_height/2);
};
translate([ruler_length+5,ruler_thickness+0.5,ruler_height/2])rotate([90,0,0])color("Gainsboro")cylinder(r=2, h= ruler_thickness+1, $fn=24);
// Measurement ticks long
for (i = [0 : 10 : ruler_length]){
translate([i, -0.05, 0]) {
color("Gray")cube([0.1, 0.1, ruler_height]);
};
};

// Measurement ticks short
for (i = [0 : 1 : ruler_length]){
translate([i, -0.05, 0]) {
color("Gray")cube([0.1, 0.1, 5]);
//translate([0,0,12]) color("Gray")cube([0.1, 0.1, 5]);
};
};

// Measurement ticks v.short
for (i = [0 : 0.5 : 50]){
translate([i, -0.05, 0]) {
color("Gray")cube([0.1, 0.1, 2.5]);
};
};

// Measurement Numbers
for (i = [10 : 10 : ruler_length]){
translate([i, -0.05, 0]) {
color("Black")translate([0,ruler_thickness,10.25]) rotate([90,0,0]) linear_extrude(height = 2.2)text(str(i), size=2, halign = "center");
};
};
};
};



Protractor

Similar to the ruler, this is great for scale and allows me to double check I've got angles correct.

module protractor(){
translate([0,0,1]) difference(){
color("Gainsboro", 0.5)cylinder(r=50, h= 2, center= true, $fn=128);
color("Gainsboro", 0.5)translate([0,-25,0])cube([100, 50, 4], center= true);

// Ruler long ticks & Numbers
for (i = [10 : 10 : 90]){
translate([-50+i, -0.05, 1.01])rotate([-90,0,0])color("Gray")cube([0.1, 0.1, 5]);
color("Black")translate([-50+i, 5.5, -0.01]) rotate([0,0,0]) linear_extrude(height = 1.05)text(str(i), size=2, halign = "center");
};

// Ruler short ticks
for (i = [0 : 5 : 95]){
translate([-50+i, -0.05, 1.01])rotate([-90,0,0])color("Gray")cube([0.1, 0.1, 2.5]);
};
for (i = [0 : 1 : 99]){
translate([-50+i, -0.05, 1.01])rotate([-90,0,0])color("Gray")cube([0.1, 0.1, 1]);
};

// Protractor Large ticks and numbers
for (i = [10 : 10 : 170]){
rotate([0,0,90-i])translate([0,5+42.5,1])color("Gray")cube([0.1, 5, 1.1], center = true);
color("Black")rotate([0,0,90-i])translate([0,42.5,0])linear_extrude(height = 1.05)text(str(i), size=2, halign = "center");
rotate([0,0,90-i])translate([0,5+22.5,1])color("Gray")cube([0.1, 25, 1.1], center = true);
};
// Protractor small ticks
for (i = [5 : 5 : 175]){
rotate([0,0,90-i])translate([0,5+44,1])color("Gray")cube([0.1, 2.5, 1.1], center = true);
};
// Protractor v.small ticks
for (i = [1 : 1 : 179]){
rotate([0,0,90-i])translate([0,5+45,1])color("Gray")cube([0.1, 1, 1.1], center = true);
};
};
};



Nut/Bolt head

This is just a simple way to take an edge-to-edge size and get the diameter of the circle needed, then doing a 6 faced cylinder (well 8 with top and bottom). It may or may not be easier to extrude a polygon but I like doing this way...

function indiameter_to_diameter(d) = d/(sqrt(3)/2);
module hexagon(indiameter, height = 2){
diameter = indiameter_to_diameter(indiameter);
translate([0,0,0]) cylinder(d=diameter, h = height, $fn=6);
};

So an M3 bolt has a 5.5mm head so I would do hexagon(5.5); most of the time I make a bolt module or a lookup so an M3 bolt has the right size shaft, head etc.

module M3_nut(){
difference(){
hexagon(indiameter = 5.5, height = 3);
translate([0,0,-1])cylinder(d=3, h=4);
};


Standoff

Using the hexagon module and indiameter function. Being able to reuse code to quick make new objects is one of the major strengths of OpenSCAD!

module M3_standoff(body_height=5, screw_height=5){<br>    body_size = 5.5;
bolt_diameter = 3;

hexagon(indiameter = body_size, height = body_height);
translate([0,0,-screw_height])cylinder(d=bolt_diameter, h=screw_height);
};

I could (and perhaps should) have made a universal standoff module then specific modules for the sizes I have.


Torus

A doughnut shape, sometimes handy particularly if you want a lip on a curved edge. It's based on the information in the wiki: OpenSCAD User Manual/2D to 3D Extrusion


module torus(thickness = 2, diameter = 20){
rotate_extrude(convexity = 10, $fn = 100)
translate([diameter/2 - thickness/2, 0, 0])
circle(d = thickness, $fn = 100);
};

There's some defaults set just so it makes something as you are building your parts.


Different views

It depends on what you are building and how it will be used. For me most of my drawings will eventually be 3D printed or laser cut. So I make multiple modules of different part layouts. The first one I call "design_view()" this lays out parts as they will be used when they exist, showing each part in the finial layout. I make another called "print_layout()" which has the parts spaced orientated for printing. This allows me to quickly move from seeing (or exporting pictures) of a design to something I can send to print.

I'll add to this as I find or make useful things. I have a load of old designs I need to dig through, I'm getting better (I think) so many of the older ones aren't great and i only tend to update them if I encounter a similar problem again.

Step 6: A Worked Example - Lamp

There's a nice lamp in the fusion 360 tutorial, this is sort of similar. There will be other ways to make the shapes, many of them much better, I'm trying to give an examples for people to play with. If you know of a better way add it to the comments!

Shade

The fusion tutorial uses a 2d shape with a rotate extrude so I will do something similar. OpenScad has three 2D primitives, circle, square and polygon. We could build the shade from scaled circles but I want to try plotting it as a curve. One way to do this is to use a function:

function sine_vec_points (range)= [for (x = range) [ x, sin(x*1.25)*100]];

This will return a set of vector co-ordinates for the range given. So for every value in 'range' we return [ x (i.e the value), and f(x) which in this case is sin(x) with a multiplier. The advantage of this is that we can swap, change or reuse my curve more easily.

Next we use the vector to plot a 2D curve. The vector points are made and used to plot a polygon. This module has a default value for range given [0:180], if you call the module with a range it overrides this.

module shade_base_2d(range = [0:180]){
curve_vec_points = sine_vec_points(range);
rotate([0,0,21]) polygon(points=curve_vec_points);
};

The base shape of the lamp is a rotate_extrude of the curve...

module shade_base(range = [0:180]){
curve_vec_points = sine_vec_points(range);
rotate_extrude(angle = 360, convexity = 4) rotate([0,0,21]) polygon(points=curve_vec_points);
};

The lamp shade is made from differencing two of the extrusions off set by -2mm and making a hole in the middle for an LED or bulb:

module shade(){<br>    difference(){
shade_base();
translate([0,0,-2])shade_base();
translate([0,0,0]) cylinder(d=50, h=150);
};
};


Base

Starting with the back foot, make a hull between two spheres.

module base(){

// Back leg
hull(){
translate([0,0,20]) sphere(d=20);
translate([-50,0,5]) sphere(d=10);
};
};
base();

To make the foot a little more streamlined, scale the sphere:

module base(){

// Back leg
hull(){
translate([0,0,20]) sphere(d=20);
translate([-50,0,5]) scale([1,0.75,1]) sphere(d=10);
};
};
base();

Next add two front feet in a similar manner, make two spheres and hull between them:

module base(){
// Back leg
hull(){
translate([0,0,20]) sphere(d=20);
translate([-50,0,5]) scale([1,0.75,1]) sphere(d=10);
};
// Front left
hull(){
translate([0,0,20]) sphere(d=20);
translate([60,-40,5]) rotate([0,0,-30]) scale([1,0.5,1]) sphere(d=10);
};
// Front right
hull(){
translate([0,0,20]) sphere(d=20);
translate([60,40,5]) rotate([0,0,30]) scale([1,0.5,1]) sphere(d=10);
};
};
base();

This time I've rotated the scaled spheres a little.

Arm

Most of the dimensions for the arm were set by how I laid out the base and shade. The lower arm is a cylinder, scaled 1.5 fold along the x axis to make it oval. The upper arm is two parallel scaled cylinders.

I made a joining plate for articulation:

module plate(){
rotate([90,0,0]) difference(){
hull(){
translate([0,10,0]) cylinder(d=10, h=3);
translate([10,0,0]) cylinder(d=10, h=3);
translate([-10,0,0]) cylinder(d=10, h=3);
};

translate([0,10,-1]) cylinder(d=3, h=6);
translate([10,0,-1]) cylinder(d=3, h=6);
translate([-10,0,-1]) cylinder(d=3, h=6);
};
};

This used a hull on three cylinders, the same co-ordinates were used to difference out some bolt holes.

Assembly

With the parts all sorted it's now time to put them together, I've also coloured them at this stage. Here the lengths and angles of the arm parts are tweaked to make everything join. There's a nice color table in the wiki book to help get things looking nice in the preview.

module design_view(){
translate([55,0,120]) rotate([0,-30,0]) scale([0.25,0.25,0.25]) color("WhiteSmoke") shade();
translate([0,0,20]) rotate([0,0,0]) arm();
translate([0,0,0]) rotate([0,0,0]) color("DimGray") base();
};

Step 7: Exporting for the Real World

3D Printing

To export an object you need to first render it with F6. This can take a little while depending on the complexity of your design, how it's been made ($fn, hull etc.) and your hardware.

Once rendered the object can be exported as an .stl and then imported into your favorite slicer!

With most (all?) of the printers I've used so far I find smaller holes (M2 and M3) need to be made a smidge bigger for printing, It's worth trying a quick test print early on. For my Ender3 I use 3.5mm as an M3 through hole (it should be 3.4mm) because I get a little slump and would need to use a needle file to get things perfect.

Laser cutting

Exporting for laser cutting needs an extra couple of steps.

BE AWARE: OpenSCAD is inherently dimensionless, most packages assume you are working in mm but every so often you find one which doesn't. I had an issue with a another package reading my dimensions as px, using 72px inch and making everything tiny, and I heard a horror story of a drawing being imported with dimensions treated as inches not mm and it wasn't caught until after manufacture.

I make all my parts as modules so once the design is finished I make a cube the size of the stock (e.g. 500mm x 500mm acrylic) then lay out my parts inside the boundaries. Once I'm happy I've got the best/most efficient use of material then I do a projection. This make a 2D version of your 3D parts.

This works really well for sheet material, I then do a projection through it to give a 2D shape with all the holes, cut outs etc.

projection() part();

So an example would be a 5mm sheet with a few holes:

difference(){    
cube([100,100,5]);
for (i = [10:10:90]){
translate([i,50,-1]) cylinder(d=3.4, h=7);
};
};

Then the projection would be a 2D object which could be exported:

projection(cut=true) difference(){
cube([100,100,5]);
for (i = [10:10:90]){
translate([i,50,-1]) cylinder(d=3.4, h=7);
};
};

The pictures look similar but the first one has thickness, the second one is 2D.

On my system this projection takes a lot of thinking about once parts get complex.

For some laser cutting services you sometimes need to change the colour of the lines, red for cut though and green for engraving, so I export the projection as an svg file (scalable vector graphic) then open and edit in Inkscape.

Other places prefer .dxf, so far every time I've imported a .dxf it's asked about the scaling to mm and all has been good.