User:LABoyd2/function, module from manual 160328

Introduction

edit

Users can extend the language by defining their own modules and functions. This allows grouping portions of script for easy reuse with different values. Well chosen names also help document your script.

OpenSCAD provides:

functions which return values.
modules which perform actions, but do not return values.

OpenSCAD calculates the value of variables at compile-time, not run-time. The last variable assignment within a scope will apply everywhere in that scope. It also applies to any inner scopes, or children, thereof. See Scope of variables for more details. It may be helpful to think of them as override-able constants rather than as variables.

For functions and modules OpenSCAD makes copies of pertinent portions of the script for each use. Each copy has its own scope, which contains fixed values for variables and expressions unique to that instance.

Functions

edit

Functions operate on values to calculate and return new values.

function definition
function name ( parameters ) = value ;
name
Your name for this function. A meaningful name is helpful later.
parameters
Zero or more arguments. Parameters can 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.
value
an expression which calculates a value. This value can be a vector.
function use
When used, functions are treated as values, and do not themselves end with a semi-colon ';'.
 example 1
   
 function func0() = 5;
 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:

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

It can be used to store variables in recursive functions.

Recursive functions

edit

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

Note: There is a built-in recursion limit to prevent an application crash. If the limit is hit, the result of the function call is undef.

// recursion - find the sum of the values in a vector (array)
// from the start (or s'th element) to the i'th element - remember elements are zero based

function sumv(v,i,s=0) = (i==s ? v[i] : v[i] + sumv(v,i-1,s));

vec=[ 10, 20, 30, 40 ];
echo("sum vec=", sumv(vec,2,1)); // is 20+30=50

Modules

edit

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.
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.

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

Object modules

edit

Object modules use one or more primatives, 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

edit

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}

Children

edit

Objects are indexed via integers from 0 to $children-1. OpenSCAD sets $children is 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.

 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.06 2014.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);  
 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

edit
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();}