OpenSCAD User Manual/User-Defined Functions and Modules

Users are encouraged to structure their programs by defining their own functions and modules.

Calculations that need to be done repeatedly may be encapsulated in functions, taking arguments and returning a value that may be used in an expression.

The normal operation of CSG, Constructive Solid Geometry, is to form complex shapes by the combination of primitive and derived shapes. When a complex design includes repeated instances of a component a custom Object Module may be defined. Likewise, when a sequence of operators is used repeatedly in a script it may be factored out to define an Operations Module.

Note: Names in OpenSCAD are case sensitive so test() is different from TEST() or Test()

Note: the terms "parameter" and "argument" are used interchangeably for the placeholder names used in the list of inputs to modules and functions

Definitions Limited By Scope

Functions, and modules of both types, may be named and defined locally, that is, inside a local scope. This means, of course, that calls made to those names from outside their scope will not find them and so produce a run-time error.

Function Arguments

The arguments named in the definition of a function or module must follow the rules for Named Objects. They are local variables inside the function's scope.

Argument names should not be the same as any built-in or user-defined named object, special variable, or global variable as it could redefine the name in the scope of the function.

Arguments may be assigned a default value in this form

( arg1=10, arg2="default", arg3=false )

Note: arguments, like variables, are not typed so it is good to test positional arguments or to use assert()

Functions

Functions operate on their given parameters, or arguments, to calculate and return new values. The body of a function is a single expression so to define local variables the let() operator must be used.

definition of a named function
function <name>( param=<default>, arg ) = expression;
keyword
literal "function"
name
the function name (see Rules on Names).
(parameter,argument)
Zero or more arguments Note: parameter and argument are synonyms .
'='
literal equals sign denoting the assignment operator
value
an expression that calculates a value which may be of any type.
';'
literal semi-colon, end of definition statement

Function Literal

A function literal is a way to encapsulate a calculation in an object that can then be used in place of a literal value in an expression, function or module call, or even be assigned to a variable or returned by a function.

These objects are called anonymous functions, closures, or lambdas.

[Note: Requires version 2021.01]

definition of a lambda function
function( param<default>, arg ) expression;
keyword
literal "function"
(parameter,argument)
Zero or more arguments, optionally with a default value Note: parameter and argument are synonyms .
value
an expression that calculates a value which may be of any type.

Note: the lack of a name and of the equals sign Function literals can be assigned to variables and passed around like any value. Calling the function uses the normal function call syntax with parenthesis.

func = function (x) x * x;
echo(func(5)); // ECHO: 25

When called as part of an expression lambdas are calculated before other operators.

The following script defines three lambdas: the first is assigned to the variable "selector" which, when called, will create one of the two inner lambdas to return as its result:

// assign a lambda to variable "selector"
selector = function (which)
    which == "add" ?
        function (x) x + x  // lambda for addition
      : function (x) x * x; // and for multiplication
// display the value of "selector" for "add"
echo(selector("add"));     // ECHO: function(x) ((x + x))
// call the function via selector to add 5+5
echo(selector("add")(5));  // ECHO: 10

// .. and for "mul"
echo(selector("mul"));     // ECHO: function(x) ((x * x))
// call the function via selector to do 5 x 5
echo(selector("mul")(5));  // ECHO: 25

Use of Named Functions

function trivial() = 5;  // minimum and trivial
function func1(x=3) = 2*x+1;
function func2() = [1,2,3,4];
function func3(y=7) = (y==7) ? 5 : 2 ;
function func4(p0,p1,p2,p3) = [p0,p1,p2,p3];
    
echo(func0());            // 5
a =   func1();            // 7
b =   func1(5);           // 11
echo(func2());            // [1, 2, 3, 4]
echo(func3(2), func3());  // 2, 5
   
z = func4(func0(), func1(), func2(), func3());
//  [5, 7, [1, 2, 3, 4], 5]
   
translate([0, -4*func0(), 0])
  cube([func0(), 2*func0(), func0()]);
// same as translate([0,-20,0]) cube([5,10,5]);
// example 2  creates for() range to give desired no of steps to cover range
  
function steps(start, no_steps, end) =
  [start : (end-start)/(no_steps-1) : end];
  
echo(steps(10, 3, 5));                // [10 : -2.5 : 5]
for (i = steps(10, 3, 5))  echo(i);   //  10 7.5 5
  
echo(steps(10, 3, 15));               // [10 : 2.5 : 15]
for (i = steps(10, 3, 15)) echo(i);   // 10 12.5 15
  
echo(steps(0, 5, 5));                // [0 : 1.25 : 5]
for (i = steps(0, 5, 5))   echo(i);  // 0 1.25 2.5 3.75 5
Example 3
// example 3     rectangle with top pushed over, keeping same y
  
function rhomboid(x=1, y=1, angle=90)
  = [[0,0],[x,0],
    [x+x*cos(angle)/sin(angle),y],
    [x*cos(angle)/sin(angle),y]];
    
echo (v1); v1 = rhomboid(10,10,35);  // [[0, 0], 
                                     // [10, 0], 
                                     // [24.2815, 10],
                                     // [14.2815, 10]]
polygon(v1);
polygon(rhomboid(10,10,35));         // alternate
//performing the same action with a module
   
module parallelogram(x=1,y=1,angle=90)
    {polygon([[0,0],[x,0],
              [x+x*cos(angle)/sin(angle),y],
              [x*cos(angle)/sin(angle),y]]);};
  
parallelogram(10,10,35);

You can also use the let statement to create variables in a function:

function get_square_triangle_perimeter(p1, p2) =
  let (hypotenuse = sqrt(p1*p1+p2*p2))
    p1 + p2 + hypotenuse;

It can be used to store values in recursive functions. See the wikipedia page for more information on the general concept.

Recursive functions

Recursive function calls are supported. Using the Conditional Operator "... ? ... : ... ", it is possible to ensure the recursion is terminated.

// recursion example: add all integers up to n
function add_up_to(n) = ( n==0 ? 
                                 0 : 
                                 n + add_up_to(n-1) );

There is a built-in recursion limit to prevent an application crash (a few thousands). If the limit is hit, you get an error like: ERROR: Recursion detected calling function ... .

For any tail-recursive function that calls itself, OpenSCAD is able to eliminate internally the recursion transforming it in an iterative loop.

The previous example code is not tail recursion, as the binary '+' can only execute when both its operand values are available. Its execution will therefore occur after the recursive call

add_up_to(n-1)

has generated its second operand value.

However, the following is entitled to tail-recursion elimination:

// tail-recursion elimination example: add all integers up to n
function add_up_to(n, sum=0) =
    n==0 ?
        sum :
        add_up_to(n-1, sum+n);
 
echo(sum=add_up_to(100000));
// ECHO: sum = 5.00005e+009

Tail-recursion elimination allows much higher recursion limits (up to 1000000).

Overwriting built-in functions

It is possible to overwrite the built-in functions. Note that definitions are handled first, so the evaluation does indeed return true for both echo() calls as those are evaluated in a later processing step.

Source Code Console output
echo (sin(1));
function sin(x) = true;
echo (sin(1));
Compiling design (CSG Tree generation)...
ECHO: true
ECHO: true
Compiling design (CSG Products generation)...

Modules

Modules can be used to define objects or, using children(), define operators. Once defined, modules are temporarily added to the language.

module definition
module name ( parameters ) { actions }
name
Your name for this module. Try to pick something meaningful. Currently, valid names can only be composed of simple characters and underscores [a-zA-Z0-9_] and do not allow high-ASCII or Unicode characters.
parameters
Zero or more arguments. Parameters may be assigned default values, to use in case they are omitted in the call. Parameter names are local and do not conflict with external variables of the same name.
actions
Nearly any statement valid outside a module can be included within a module. This includes the definition of functions and other modules. Such functions and modules can be called only from within the enclosing module.

Variables can be assigned, but their scope is limited to within each individual use of the module. There is no mechanism in OpenSCAD for modules to return values to the outside. See Scope of variables for more details.

Object modules

Object modules use one or more primitives, with associated operators, to define new objects.

In use, object modules are actions ending with a semi-colon ';'.

name ( parameter values );
Color bar
//example 1
   
translate([-30,-20,0])
   ShowColorBars(Expense);
   
ColorBreak=[[0,""],
           [20,"lime"],  // upper limit of color range
           [40,"greenyellow"],
           [60,"yellow"],
           [75,"LightCoral"],
           [200,"red"]];
Expense=[16,20,25,85,52,63,45];
   
module ColorBar(value,period,range){  // 1 color on 1 bar
   RangeHi = ColorBreak[range][0];
   RangeLo = ColorBreak[range-1][0];
   color( ColorBreak[range][1] ) 
   translate([10*period,0,RangeLo])
      if (value > RangeHi)      cube([5,2,RangeHi-RangeLo]);
      else if (value > RangeLo) cube([5,2,value-RangeLo]);
  }  
module ShowColorBars(values){
    for (month = [0:len(values)-1], range = [1:len(ColorBreak)-1])
      ColorBar(values[month],month,range);
}
House
//example 2
module house(roof="flat",paint=[1,0,0]) {
   color(paint)
   if(roof=="flat") { translate([0,-1,0]) cube(); }
   else if(roof=="pitched") {
     rotate([90,0,0]) linear_extrude(height=1)
     polygon(points=[[0,0],[0,1],[0.5,1.5],[1,1],[1,0]]); }
   else if(roof=="domical") {
     translate([0,-1,0]){
       translate([0.5,0.5,1]) sphere(r=0.5,$fn=20); cube(); }
} }

                   house();
translate([2,0,0]) house("pitched");
translate([4,0,0]) house("domical",[0,1,0]);
translate([6,0,0]) house(roof="pitched",paint=[0,0,1]);
translate([0,3,0]) house(paint=[0,0,0],roof="pitched");
translate([2,3,0]) house(roof="domical");
translate([4,3,0]) house(paint=[0,0.5,0.5]);
//example 3
   
element_data = [[0,"","",0],  // must be in order
    [1,"Hydrogen","H",1.008],   // indexed via atomic number
    [2,"Helium",  "He",4.003]   // redundant atomic number to preserve your sanity later
];
   
Hydrogen = 1;
Helium   = 2;
      
module coaster(atomic_number){
    element     = element_data[atomic_number][1];
    symbol      = element_data[atomic_number][2];
    atomic_mass = element_data[atomic_number][3];
    //rest of script
}

Operator modules

Children

Use of children() allows modules to act as operators applied to any or all of the objects within this module instantiation. In use, operator modules do not end with a semi-colon.

name ( parameter values ){scope of operator}

Basicly the children() command is used to apply modifications to objects that are focused by a scope:

 module myModification() { rotate([0,45,0]) children(); } 
 
 myModification()                 // The modification
 {                                // Begin focus
   cylinder(10,4,4);              // First child
   cube([20,2,2], true);          // Second child
 }                                // End focus


Objects are indexed via integers from 0 to $children-1. OpenSCAD sets $children to the total number of objects within the scope. Objects grouped into a sub scope are treated as one child. See example of separate children below and Scope of variables. Note that children(), echo() and empty block statements (including ifs) count as $children objects, even if no geometry is present (as of v2017.12.23).

 children();                         all children
 children(index);                    value or variable to select one child
 children([start : step : end]);     select from start to end incremented by step
 children([start : end]);            step defaults to 1 or -1
 children([vector]);                 selection of several children

Deprecated child() module

Up to release 2013.06 the now deprecated child() module was used instead. This can be translated to the new children() according to the table:

up to 2013.062014.03 and later
child()children(0)
child(x)children(x)
for (a = [0:$children-1]) child(a)children([0:$children-1])
Use all children

Examples

//Use all children
    
module move(x=0,y=0,z=0,rx=0,ry=0,rz=0)
{ translate([x,y,z])rotate([rx,ry,rz]) children(); }
   
move(10)           cube(10,true);
move(-10)          cube(10,true);
move(z=7.07, ry=45)cube(10,true);
move(z=-7.07,ry=45)cube(10,true);
Use only the first child, multiple times
//Use only the first child, multiple times
  
module lineup(num, space) {
   for (i = [0 : num-1])
     translate([ space*i, 0, 0 ]) children(0);
}

lineup(5, 65){ sphere(30);cube(35);}
Separate action for each child
  //Separate action for each child
   
  module SeparateChildren(space){
    for ( i= [0:1:$children-1])   // step needed in case $children < 2  
      translate([i*space,0,0]) {children(i);text(str(i));}
  }
   
  SeparateChildren(-20){
    cube(5);              // 0
    sphere(5);            // 1
    translate([0,20,0]){  // 2
      cube(5);
      sphere(5);
    }     
    cylinder(15);         // 3
    cube(8,true);         // 4
  }
  translate([0,40,0])color("lightblue")
    SeparateChildren(20){cube(3,true);}
Multiple ranges
//Multiple ranges
module MultiRange(){
   color("lightblue") children([0:1]);
   color("lightgreen")children([2:$children-2]);
   color("lightpink") children($children-1);
}
   
MultiRange()
{
   cube(5);              // 0
   sphere(5);            // 1
   translate([0,20,0]){  // 2
     cube(5);
     sphere(5);
   }     
   cylinder(15);         // 3
   cube(8,true);         // 4
}

Further module examples

Objects
module arrow(){
    cylinder(10);
    cube([4,.5,3],true);
    cube([.5,4,3],true);
    translate([0,0,10]) cylinder(4,2,0,true);
}
  
module cannon(){
    difference(){union()
      {sphere(10);cylinder(40,10,8);} cylinder(41,4,4);
} }
  
module base(){
    difference(){
      cube([40,30,20],true);
      translate([0,0,5])  cube([50,20,15],true);
} }
Operators
Rotary Clusters
module aim(elevation,azimuth=0)
    { rotate([0,0,azimuth])
      { rotate([0,90-elevation,0]) children(0);
      children([1:1:$children-1]);   // step needed in case $children < 2
} }
   
aim(30,20)arrow();
aim(35,270)cannon();
aim(15){cannon();base();}

module RotaryCluster(radius=30,number=8)
    for (azimuth =[0:360/number:359])
      rotate([0,0,azimuth])    
        translate([radius,0,0]) { children();
          translate([40,0,30]) text(str(azimuth)); }
   
RotaryCluster(200,7) color("lightgreen") aim(15){cannon();base();}
rotate([0,0,110]) RotaryCluster(100,4.5) aim(35)cannon();
color("LightBlue")aim(55,30){cannon();base();}

Recursive modules

Like functions, modules may contain recursive calls. However, there is no tail-recursion elimination for recursive modules.

The code below generates a crude model of a tree. Each tree branch is itself a modified version of the tree and produced by recursion. Be careful to keep the recursion depth (branching) n below 7 as the number of primitives and the preview time grow exponentially.

A simple tree created with a recursive OpenSCAD module
    module simple_tree(size, dna, n) {   
        if (n > 0) {
            // trunk
            cylinder(r1=size/10, r2=size/12, h=size, $fn=24);
            // branches
            translate([0,0,size])
                for(bd = dna) {
                    angx = bd[0];
                    angz = bd[1];
                    scal = bd[2];
                        rotate([angx,0,angz])
                            simple_tree(scal*size, dna, n-1);
                }
        }
        else { // leaves
            color("green")
            scale([1,1,3])
                translate([0,0,size/6]) 
                    rotate([90,0,0]) 
                        cylinder(r=size/6,h=size/10);
        }
    }
    // dna is a list of branching data bd of the tree:
    //      bd[0] - inclination of the branch
    //      bd[1] - Z rotation angle of the branch
    //      bd[2] - relative scale of the branch
    dna = [ [12,  80, 0.85], [55,    0, 0.6], 
            [62, 125, 0.6], [57, -125, 0.6] ];
    simple_tree(50, dna, 5);

Another example of recursive module may be found in Tips and Tricks

Overwriting built-in modules

It is possible to overwrite the built-in modules.

A simple, but pointless example would be:

module sphere(){
    square();
}
sphere();

Note that the built-in sphere module can not be called when over written.

A more sensible way to use this language feature is to overwrite the 3D primitives with extruded 2D-primitives. This allows additional customization of the default parameters and adding additional parameters.

Category:Book:OpenSCAD User Manual#User-Defined%20Functions%20and%20Modules%20
Category:Book:OpenSCAD User Manual Category:Pages with syntax highlighting errors