OpenSCAD User Manual/Tips and Tricks

From Wikibooks, open books for an open world
Jump to: navigation, search

A note on licensing[edit]

All code snippets shown on this page are intended to be used freely without any attribution and for any purpose, e.g. consider any code contribution here to be placed under Public Domain or CC0 license. This is not meant to change the normal license of the page as a whole and/or the manual itself.

Data[edit]

Map values from a list[edit]

// The function that maps input values x to output values, the
// example uses floor() to convert floating point to integer
// values.
function map(x) = floor(x);
  
input = [58.9339, 22.9263, 19.2073, 17.8002, 40.4922, 19.7331, 38.9541, 28.9327, 18.2059, 75.5965];
  
// Use a list comprehension expression to call the map() function
// for every value of the input list and put the result of the
// function in the output list.
output = [ for (x = input) map(x) ];
  
echo(output);
// ECHO: [58, 22, 19, 17, 40, 19, 38, 28, 18, 75]

Filter values in a list[edit]

// The function that define if the input value x should be
// included in the filtered list, the example will select
// all even values that are greater than 6.
function condition(x) = (x >= 6) && (x % 2 == 0);
  
input = [3, 3.3, 4, 4.1, 4.8, 5, 6, 6.3, 7, 8];
  
// Use a list comprehension expression to call the condition()
// function for every value of the input list and put the value
// in the output list if the function returns true.
output = [ for (x = input) if (condition(x)) x ];
  
echo(output);
// ECHO: [6, 8]

Add all values in a list[edit]

// Create a simple recursive function that adds the values of a list of floats;
// the simple tail recursive structure makes it possible to
// internally handle the calculation as loop, preventing a
// stack overflow.
function add(v, i = 0, r = 0) = i < len(v) ? add(v, i + 1, r + v[i]) : r;
 
input = [2, 3, 5, 8, 10, 12];
 
output = add(input);

echo(output);
// ECHO: 40
//------------------ add2 -----------------------
// An even simpler non recursive code version of add explores the 
// the matrix product operator
function add2(v) = [for(p=v) 1]*v;

echo(add2(input));
// ECHO: 40

// add2 works also with lists of vectors
input2 = [ [2, 3] , [5, 8] , [10, 12] ];
echo(add2(input2));
// ECHO: [17, 23]
echo(add(input2));
// ECHO: undef  // Why?
//----------------- add3 --------------------------
// With a little more code, the function add may be used also 
// to add any homogeneous list structure of floats
function add3(v, i = 0, r) = 
    i < len(v) ? 
        i == 0 ?
            add3(v, 1, v[0]) :
            add3(v, i + 1, r + v[i]) :
        r;

input3 = [ [[1], 1] , [[1], 2] , [[1], 3] ];
input4 = [ 10, [[1], 1] , [[1], 2] , [[1], 3] ];

echo(add3(input3));
// ECHO: [[3], 6]
echo(add2(input3));
// ECHO: undef // input3 is not a list of vectors
echo(add3(input4));
// ECHO: undef // input4 is not a homogeneous list

Count values in a list matching a condition[edit]

// The function that define if the input value x should be
// included in the filtered list, the example will select
// all even values that are greater than 6.
function condition(x) = (x >= 6) && (x % 2 == 0);
 
input = [3, 3.3, 4, 4.1, 4.8, 5, 6, 6.3, 7, 8];
 
// Use a list comprehension expression to call the condition()
// function for every value of the input list and put the value
// in the output list if the function returns true.
// Finally the count is determined simply by using len() on the
// filtered list.
output = len([ for (x = input) if (condition(x)) x ]);
 
echo(output);
// ECHO: 2

Find the index of the maximum value in a list[edit]

// Create a function that find the index of the maximum value
// found in the input list of floats
function index_max(l) = search(max(l), l)[0];

input = [ 6.3, 4, 4.1, 8, 7, 3, 3.3, 4.8, 5, 6];

echo(index_max(input));
// Check it
echo(input[index_max(input)] == max(input));
// ECHO: 3
// ECHO: true

Caring about undef[edit]

Most ilegal operations in OpenSCAD return undef. Some return nan. However the program keeps running and undef values may cause unpredictable future behaviour if no precaution is taken. When a function argument is missing in a function call an undef value is assigned to it in evaluating the function expression. To avoid this a default value may be assigned to optional function arguments.

// add 'a' to each element of list 'L'
function incrementBy(L, a) =  [ for(x=L) x+a ];

//add 'a' to each element of list 'L'; 'a' default is 1 when missing
function incrementByWithDefault(L, a=1) = [ for(x=L) x+a ];

echo(incrementBy= incrementBy([1,2,3],2));
echo(incrementByWithDefault= incrementByWithDefault([1,2,3],2));
echo(incrementBy= incrementBy([1,2,3]));
echo(incrementByWithDefault= incrementByWithDefault([1,2,3]));
// ECHO: incrementBy= [3, 4, 5]
// ECHO: incrementByWithDefault= [3, 4, 5]
// ECHO: incrementBy= [undef, undef, undef]
// ECHO: incrementByWithDefault= [2, 3, 4]

Sometimes the default value depends on other parameters of the call and cannot be set as before; a conditional expression solve this:

// find the sublist of 'list' with indices from 'from' to 'to' 
function sublist(list, from=0, to) =
    let( end = (to==undef ? len(list)-1 : to) )
    [ for(i=[from:end]) list[i] ];

echo(s0= sublist(["a", "b", "c", "d"]) );  	// from = 0, end = 3
echo(s1= sublist(["a", "b", "c", "d"], 1, 2) ); // from = 1, end = 2
echo(s2= sublist(["a", "b", "c", "d"], 1)); 	// from = 1, end = 3
echo(s3= sublist(["a", "b", "c", "d"], to=2) );	// from = 0, end = 2
// ECHO: s0 = ["a", "b", "c", "d"]
// ECHO: s1 = ["b", "c"] 
// ECHO: s2 = ["b", "c", "d"] 
// ECHO: s3 = ["a", "b", "c"]

The function sublist() returns undesirable values when from > to and generates a warning (try it!). A simple solution would be to return the empty list [] in this case:

// returns an empty list when 'from > to' 
function sublist2(list, from=0, to) =
    from<=to ?
    	let( end = (to==undef ? len(list)-1 : to) )
    	[ for(i=[from:end]) list[i] ] :
	[];

echo(s1= sublist2(["a", "b", "c", "d"], 3, 1));
echo(s2= sublist2(["a", "b", "c", "d"], 1));
echo(s3= sublist2(["a", "b", "c", "d"], to=2));
// ECHO: s1 = []
// ECHO: s2 = []
// ECHO: s3 = ["a", "b", "c"]

The output s2 above is the empty list because to==undef and the comparison of from and to evaluates as false: the default value of to has been lost. To overcome this it is enough to invert the test:

function sublist3(list, from=0, to) =
    from>to ?
	[] :
    	let( end = to==undef ? len(list)-1 : to )
    	[ for(i=[from:end]) list[i] ] ;

echo(s1=sublist3(["a", "b", "c", "d"], 3, 1));
echo(s2=sublist3(["a", "b", "c", "d"], 1));
echo(s3=sublist3(["a", "b", "c", "d"], to=2));
// ECHO: s1 = []
// ECHO: s2 = ["b", "c", "d"]
// ECHO: s3 = ["a", "b", "c"]

Now, when to is undefined, the first test will evaluate as false and the let() is executed. So, with careful choices of tests we can deal with undef values.

Geometry[edit]

Stack cylinders on top of each other[edit]

OpenSCAD - Stacked Cylinders
// Define the sizes for the cylinders, first value is the
// radius, the second is the height.
// All cylinders are to be stacked above each other (with
// an additional spacing of 1 unit).
sizes = [ [ 26, 3 ], [ 20, 5 ], [ 11, 8 ],  [ 5, 10 ], [ 2, 13 ] ];

// One option to solve this is by using a recursive module
// that will create a new translated coordinate system before
// going into the next level.
module translated_cylinder(size_vector, idx = 0) {
    if (idx < len(size_vector)) {
        radius = size_vector[idx][0];
        height = size_vector[idx][1];

        // Create the cylinder for the current level.
        cylinder(r = radius, h = height);

        // Recursive call generating the next cylinders
        // translated in Z direction based on the height
        // of the current cylinder
        translate([0, 0, height + 1]) {
            translated_cylinder(size_vector, idx + 1);
        }
    }
}

// Call the module to create the stacked cylinders.
translated_cylinder(sizes);

Minimum rotation problem[edit]

In 2D, except in very special cases, there is only two rotations that make a vector to align to another one. In 3D, there are infinite many. Only one, however, has the minimum rotation angle. The following function builds the matrix for that minimum rotation. The code is a simplification of a function found in the Oskar Linde's sweep.scad.

// Find the unitary vector with direction v. Fails if v=[0,0,0].
function unit(v) = norm(v)>0 ? v/norm(v) : undef; 
// Find the transpose of a rectangular matrix
function transpose(m) = // m is any rectangular matrix of objects
  [ for(j=[0:len(m[0])-1]) [ for(i=[0:len(m)-1]) m[i][j] ] ];
// The identity matrix with dimension n
function identity(n) = [for(i=[0:n-1]) [for(j=[0:n-1]) i==j ? 1 : 0] ];

// computes the rotation with minimum angle that brings a to b
// the code fails if a and b are opposed to each other
function rotate_from_to(a,b) = 
    let( axis = unit(cross(a,b)) )
    axis*axis >= 0.99 ? 
        transpose([unit(b), axis, cross(axis, unit(b))]) * 
            [unit(a), axis, cross(axis, unit(a))] : 
        identity(3);

Drawing "lines" in OpenSCAD[edit]

OpenSCAD - Knot
// An application of the minimum rotation
// Given to points p0 and p1, draw a thin cylinder with its
// bases at p0 and p1
module line(p0, p1, diameter=1) {
    v = p1-p0;
    translate(p0)
        // rotate the cylinder so its z axis is brought to direction v
        multmatrix(rotate_from_to([0,0,1],v))
            cylinder(d=diameter, h=norm(v), $fn=4);
}
// Generate the polygonal points for the knot path 
knot = [ for(i=[0:2:360])
         [ (19*cos(3*i) + 40)*cos(2*i),
           (19*cos(3*i) + 40)*sin(2*i),
            19*sin(3*i) ] ];
// Draw the polygonal a segment at a time
for(i=[1:len(knot)-1]) 
    line(knot[i-1], knot[i], diameter=5);
// Line drawings with this function is usually excruciatingly lengthy to render
// Use it just in preview mode to debug geometry

Another approach to the module line() is found in Rotation rule help.

Fit text into a given area[edit]

There is currently no way to query the size of the geometry generated by text(). Depending on the model it might be possible to calculate a rough estimate of the text size and fit the text into the known area. This works using resize() with the assumption the length is the dominating value.

OpenSCAD - Fitting text into a given area
// Generate 2 random values between 10 and 30
r = rands(10, 30, 2);

// Calculate width and length from random values
width = r[1];
length = 3 * r[0];

difference() {
    // Create border
    linear_extrude(2, center = true)
        square([length + 4, width + 4], center = true);
    // Cut the area for the text
    linear_extrude(2)
        square([length + 2, width + 2], center = true);
    // Fit the text into the area based on the length
    color("green")
        linear_extrude(1.5, center = true, convexity = 4)
                resize([length, 0], auto = true)
                    text("Text goes here!", valign = "center", halign = "center");
}

Create a mirrored object while retaining the original[edit]

The mirror() module just transforms the existing object, so it can't be used to generate symmetrical objects. However using the children() module, it's easily possible define a new module mirror_copy() which will generate the mirrored object in addition to the original one.

OpenSCAD - Mirror Copy
// A custom mirror module that retains the original
// object in addition to the mirrored one.
module mirror_copy(v = [1, 0, 0]) {
    children();
    mirror(v) children();
}

// Define example object.
module object() {
    translate([5, 5, 0]) {
        difference() {
            cube(10);
            cylinder(r = 8, h = 30, center = true);
        }
    }
}

// Call mirror_copy twice, once using the default which
// will create a duplicate mirrored on X axis and
// then mirror again on Y axis.
mirror_copy([0, 1, 0])
    mirror_copy()
        object();

Arrange parts on a spatial array[edit]

An operator to display a set of objects on an array.

OpenSCAD - An array of objects
 // Arrange its children in a regular rectangular array
 //      spacing - the space between children origins
 //      n       - the number of children along x axis
 module arrange(spacing=50, n=5) {
    nparts = $children;
    for(i=[0:1:n-1], j=[0:nparts/n])
        if (i+n*j < nparts)
            translate([spacing*(i+1), spacing*j, 0]) 
                children(i+n*j);
 }

 arrange(spacing=30,n=3) {
    sphere(r=20,$fn=8);
    sphere(r=20,$fn=10);
    cube(30,center=true);
    sphere(r=20,$fn=14);
    sphere(r=20,$fn=16);
    sphere(r=20,$fn=18);
    cylinder(r=15,h=30);
    sphere(r=20,$fn=22);
 }

A handy operator to display a lot of parts of a project downloaded from Thingiverse.

Note: the following usage fails:

 arrange() for(i=[8:16]) sphere(15, $fn=i);

because the for statement do an implicit union of the inside objects creating only one child.

Rounding polygons[edit]

Polygons may be rounded by the offset operator in several forms.

The roundings of a polygon by OpenSCAD operator offset()
 p = [ [0,0], [10,0], [10,10], [5,5], [0,10]];

 polygon(p);
 // round pointed vertices and enlarge
 translate([-15, 0])
 offset(1,$fn=24) polygon(p);
 // round concavities and shrink
 translate([-30, 0])
 offset(-1,$fn=24) polygon(p);
 // round concavities and preserve polygon dimensions
 translate([15, 0])
 offset(-1,$fn=24) offset(1,$fn=24) polygon(p);
 // round pointed vertices and preserve polygon dimensions
 translate([30, 0])
 offset(1,$fn=24) offset(-1,$fn=24) polygon(p);
 // round all vertices and preserve polygon dimensions
 translate([45, 0])
 offset(-1,$fn=24) offset(1,$fn=24) 
 offset(1,$fn=24) offset(-1,$fn=24) polygon(p);

Filleting objects[edit]

Filleting is the 3D counterpart of the rounding of polygons. There is no offset() operators for 3D objects, but it may be coded using minkowski operator.

OpenSCAD - Filleting an object
 render()
 difference(){
     offset_3d(2) offset_3d(-2) // exterior fillets
         offset_3d(4) offset_3d(-4) // interior fillets
             basic_model();
     // hole without fillet
     translate([0,0,10]) 
         cylinder(r=18,h=50);
 }

 module basic_model(){
     cylinder(r=25,h=55);
     cube([80,80,10], center=true);
 }

 module offset_3d(r=1, size=1e12) {
     n = $fn==undef ? 12: $fn;
     if(r==0) children();
     else 
         if( r>0 )
             minkowski(){
                 children();
                 sphere(r, $fn=n);
             }
     else {
         size2 = size*[1,1,1];
         size1 = size2*0.99;
         difference(){
             cube(size2, center=true);
             minkowski(){
                 difference(){
                     cube(size1, center=true);
                     children();
                 }
                 sphere(-r, $fn=n);
             }
         }
     }
 }

Note that this is a very time consuming process. The minkowski operator adds vertices to the model so each new offset_3d takes longer than the previous one.

Computing a bounding box[edit]

There is no way to get the bounding box limits of an object with OpenSCAD codes. However, it is possible to compute its bounding box volume. Its concept is simple: hull() the projection of the model on each axis (1D sets) and minkowski() them. As there is no way to define a 1D set in OpenSCAD, the projections are aproximated by a stick whose length is the size of the projection.

module bbox() { 

    // a 3D approx. of the children projection on X axis 
    module xProjection() 
        translate([0,1/2,-1/2]) 
            linear_extrude(1) 
                hull() 
                    projection() 
                        rotate([90,0,0]) 
                            linear_extrude(1) 
                                projection() children(); 
  
    // a bounding box with an offset of 1 in all axis
    module bbx()  
        minkowski() { 
            xProjection() children(); // x axis
            rotate(-90)               // y axis
                xProjection() rotate(90) children(); 
            rotate([0,-90,0])         // z axis
                xProjection() rotate([0,90,0]) children(); 
        } 
    
    // offset children() (a cube) by -1 in all axis
    module shrink()
      intersection() {
        translate([ 1, 1, 1]) children();
        translate([-1,-1,-1]) children();
      }

   shrink() bbx() children(); 
}
OpenSCAD - The bounding box of an object

The image shows the (transparent) bounding box of a red model generated by the code:

module model()
  color("red") 
  union() {
    sphere(10);
    translate([15,10,5]) cube(10);
  }

model();
%bbox() model();

The cubes in the offset3D operator code of the Filleting objects tip could well be replaced by the object bounding box dispensing the artificial argument size.