跳至內容

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