跳转到内容

OpenSCAD用户手册/用户自定义函数及模块

维基教科书,自由的教学读本

介绍

[编辑]

用户可通过定义自己的模块与函数来扩展OpenSCAD语言。利用此功能可组合多个的脚本片段,便于用户以不同的数值对其进行复用。选取合适的名称有助于您对自己的脚本进行文档化。

OpenSCAD提供了:

返回数值的函数
执行特定动作却不返回数值的模块

OpenSCAD在编译时计算变量的值,而非运行时。 在作用域中为变量最后一次赋的值将应用于整个作用域中。此值还可用于其内部域或子域中。 参见变量的作用域来获取更多细节。 将OpenSCAD中的变量想象为可重写(override-able)的常量而不是传统意义上的变量或许有助于您对此概念理解。

对于函数与模块而言,OpenSCAD会为每次使用的脚本内容制作一个副本。每个副本都有其自己的作用域,其中存有变量的固定值与唯一用于对应实例的表达式。

函数或模块的名称对大小写敏感,因此test()TEST() 是两种不同的函数/模块。

作用域

[编辑]

可以在一个模块内定义模块与函数,它们仅在那个模块的作用域中可见。

例如

function parabola(f,x) = ( 1/(4*f) ) * x*x; 
module plotParabola(f,wide,steps=1) {
  function y(x) = parabola(f,x);
  module   plot(x,y) {
    translate([x,y])
      circle(1,$fn=12);
  }
  xAxis=[-wide/2:steps:wide/2];
  for (x=xAxis) 
    plot(x,y(x));
}
color("red")  plotParabola(10,100,5);
color("blue") plotParabola(4,60,2);

不能在全局作用域中调用函数y()与模块plot()。

函数

[编辑]

函数会针对数值进行计算并返回新值。

函数的定义
function name ( parameters ) = value ;
name
您为此函数起的名称。直观的函数名会在后面帮您的大忙。
parameters
0个或多个参数。可以为参数们赋予默认值,要使用默认值就要在调用函数时不填写参数。参数名仅用于本函数的作用域内,不会与外部的同名变量冲突。
value
一个计算值的表达式。此项可以为一个向量。
function use
在调用函数时,可将其看作数值,不用以分号';'结尾。
// 示例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()]);
// 与translate([0,-20,0])cube([5,10,5]);等效
// 示例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
示例3
// 示例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);

您也可以使用let语句:

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

可用它在递归函数中保存变量。

递归函数

[编辑]

OpenSCAD支持调用递归函数。利用条件运算符"... ? ... : ... "来保证递归可终止。

 // 递归示例:将所有整数累加至n
 function add_up_to(n) = ( n==0 ? 0 : n + add_up_to(n-1) );

OpenSCAD为防止应用崩溃而设置了内建递归限制(大约几千)。如果达到此上限,您将得到这样的错误消息:ERROR: Recursion detected calling function ... 。For some special cases of tail-recursive functions, OpenSCAD is able to eliminate internally the recursion transforming it in an iterative loop. The special forms are:

function recurse(...) = <test> ?  <result> : recurse(...);

and

function recurse(...) = <test> ?  recurse(...) : <result>;

The previous example code does not match any of these forms. But 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.

模块

[编辑]

模块可用于定义对象、运算符,或使用children()。 模块一旦定义,就可以将其临时添加至语言特性中。

模块的定义
module name ( parameters ) { actions }
name
您为模块起的名字。试着起有意义的模块名吧~
parameters
0或多个参数。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 only be called 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 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
//示例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
//示例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
}

运算符模块

[编辑]

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

[编辑]

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

废止的child()模块

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

示例

//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
}

更多的模块示例

[编辑]
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();}

递归模块

[编辑]

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

覆写内建模块

[编辑]

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 to customize the default parameters and to add additional parameters.

覆写内建函数

[编辑]

It is possible to overwrite the built-in functions.

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