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

// 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) );
有一個內建的遞迴限制以防止應用程式崩潰(幾千個)。如果達到限制,您將收到類似以下錯誤:ERROR: Recursion detected calling function ... 。
對於所有尾遞迴 函式呼叫自身,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 以下,因為基元數量和預覽時間呈指數增長。

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 模組。
使用此語言功能的更明智方法是使用擠出的 2D 形狀覆蓋 3D 形狀。這允許自定義預設引數並新增額外的引數。