跳轉到內容

OpenSCAD 使用者手冊/使用者定義函式和模組

來自華夏公益教科書

使用者可以透過定義自己的函式模組來擴充套件語言。這允許將指令碼的部分內容分組以便輕鬆地以不同的值重複使用。精心選擇的名稱也有助於記錄您的指令碼。

函式返回數值。

模組執行操作,但不返回值。

OpenSCAD 在編譯時計算變數的值,而不是在執行時計算。範圍內最後一個變數賦值適用於該範圍內的所有地方。它也適用於任何內部範圍或子項。有關更多詳細資訊,請參見變數範圍。將它們視為可覆蓋的常量而不是變數可能會有所幫助。

對於函式和模組,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 ;
名稱
您為該函式起的名字。一個有意義的名稱在以後會有所幫助。目前有效的名稱只能由簡單字元和下劃線 [a-zA-Z0-9_] 組成,並且不允許使用高 ASCII 或 Unicode 字元。
引數
零個或多個引數。可以為引數分配預設值,以備在呼叫中省略它們時使用。引數名稱是區域性的,不會與具有相同名稱的外部變數衝突。
計算值的表示式。此值可以是向量。

函式使用

[編輯 | 編輯原始碼]

使用時,函式被視為值,它們本身不以分號 ; 結尾。

// 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
示例 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);

您還可以使用let 語句 在函式中建立變數

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

它可以用於在遞迴函式中儲存值。有關一般概念的更多資訊,請參見維基百科頁面

遞迴函式

[編輯 | 編輯原始碼]

遞迴 函式呼叫受支援。使用條件運算子 "... ? ... : ...",可以確保遞迴終止。

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

存在內建的遞迴限制以防止應用程式崩潰(幾千次)。如果達到限制,您會收到類似以下錯誤:錯誤:在呼叫函式 ... 時檢測到遞迴。

對於所有尾遞迴 函式,OpenSCAD 能夠在內部消除遞迴,將其轉換為迭代迴圈。前面的示例程式碼不是尾呼叫,因為需要在呼叫後計算“add”操作。但是,以下情況有資格進行尾遞迴消除

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

尾遞迴消除允許更高的遞迴限制(高達 1000000)。

函式字面量

[編輯 | 編輯原始碼]

[注意: 需要版本 2021.01]

函式字面量 是定義函式的表示式,其他名稱包括 lambda 或閉包。

函式字面量
function (x) x + x

函式字面量可以分配給變數,並像任何值一樣傳遞。呼叫函式使用帶有括號的正常函式呼叫語法。

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

可以定義返回函式的函式。未繫結的變數透過詞法範圍捕獲。

a = 1;
selector = function (which)
             which == "add"
             ? function (x) x + x + a
             : function (x) x * x + a;
             
echo(selector("add"));     // ECHO: function(x) ((x + x) + a)
echo(selector("add")(5));  // ECHO: 11

echo(selector("mul"));     // ECHO: function(x) ((x * x) + a)
echo(selector("mul")(5));  // ECHO: 26

覆蓋內建函式

[編輯 | 編輯原始碼]

可以覆蓋內建函式。請注意,定義首先處理,因此評估確實對兩個 echo() 呼叫返回 true,因為這些呼叫在後面的處理步驟中進行評估。

原始碼 控制檯輸出
echo (sin(1));
function sin(x) = true;
echo (sin(1));
Compiling design (CSG Tree generation)...
ECHO: true
ECHO: true
Compiling design (CSG Products generation)...

模組可以用來定義物件,或者使用 children() 定義運算子。一旦定義,模組就會暫時新增到語言中。

模組定義
module name ( parameters ) { actions }
名稱
您為該模組起的名字。嘗試選擇一個有意義的名字。目前有效的名稱只能由簡單字元和下劃線 [a-zA-Z0-9_] 組成,並且不允許使用高 ASCII 或 Unicode 字元。
引數
零個或多個引數。可以為引數分配預設值,以備在呼叫中省略它們時使用。引數名稱是區域性的,不會與具有相同名稱的外部變數衝突。
操作
幾乎所有在模組之外有效的語句都可以包含在模組內。這包括函式和其他模組的定義。這些函式和模組只能從封閉模組內呼叫。

可以分配變數,但它們的範圍僅限於每個單獨使用模組的範圍內。OpenSCAD 中沒有機制讓模組將值返回到外部。有關更多詳細資訊,請參見變數範圍

物件模組

[編輯 | 編輯原始碼]

物件模組使用一個或多個基元以及相關的運算子來定義新物件。

使用時,物件模組是以分號 ; 結尾的操作。

name ( parameter values );
顏色條
//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);
}
房子
//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
}

運算子模組

[編輯 | 編輯原始碼]

使用 `children()` 允許模組充當作用於此模組例項中任何或所有物件的運算子。在使用中,運算子模組不以分號結尾。

name ( parameter values ){scope of operator}

基本上,`children()` 命令用於對由範圍聚焦的物件進行修改。

 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


物件透過從 0 到 `$children-1` 的整數索引。OpenSCAD 將 `$children` 設定為範圍內的物件總數。分組到子範圍的物件被視為一個子物件。 參見下面單獨子物件的示例變數範圍。請注意,`children()`、`echo()` 和空塊語句(包括 `if` 語句)被視為 `$children` 物件,即使沒有幾何圖形存在(截至 2017.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()` 模組

截至 2013.06 版本,現在已棄用的 `child()` 模組被使用。可以根據下表將其轉換為新的 `children()`。

截至 2013.06 2014.03 及以後
`child()` `children(0)`
`child(x)` `children(x)`
`for (a = [0:$children-1]) child(a)` `children([0:$children-1])`
使用所有子物件

示例

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

更多模組示例

[edit | edit source]
物件
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);
} }
運算子
旋轉叢集
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();}

遞迴模組

[edit | edit source]

與函式類似,模組可以包含遞迴呼叫。但是,遞迴模組沒有尾遞迴消除。

以下程式碼生成了樹的粗略模型。每個樹枝本身都是樹的修改版本,由遞迴生成。請注意將遞迴深度(分支)n 保持在 7 以下,因為基本圖形的數量和預覽時間呈指數增長。

使用遞迴 OpenSCAD 模組建立的簡單樹
    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);

遞迴模組的另一個示例可以在 技巧和竅門 中找到。

覆蓋內建模組

[edit | edit source]

可以覆蓋內建模組。

一個簡單但無意義的示例是

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

請注意,覆蓋後無法呼叫內建的 `sphere` 模組。

使用這種語言功能更明智的方法是使用拉伸的二維圖形覆蓋三維圖形。這樣可以進一步自定義預設引數,並新增其他引數。

華夏公益教科書