children 子模組

February 26, 2022

在〈OpenSCAD CheatSheet〉中,對 Transformations、Boolean operations 等模組來說,後續指定的單一模組或是 {} 中指定的多個模組,是 Transformations、Boolean operations 等模組的子模組。

子模組抽象化

如果想要個圓環要怎麼做呢?基本上可以有兩種方式。第一個方式是畫一個大圓、一個小圓,然後用 difference。例如:

module circle_ring(radius, thickness) {
    difference() {
        circle(radius);
        circle(radius - thickness);
    }
}

circle_ring(3, 1, $fn = 48);

另一個方式是使用 offset,可以指定 rdeltachamfer,在〈offset〉有兩張圖,可以得知道它們的作用:

children 子模組

可以撰寫以下的程式:

module circle_ring(radius, thickness) {
    difference() {
        circle(radius);
        offset(r = -thickness) 
            circle(radius);
    }
}

circle_ring(3, 1, $fn = 48);

兩個範例的結果是相同的:

children 子模組

若要做一個框呢?類似地…

module square_frame(size, thickness, center = false) {
    difference() {
        square(size, center);
        offset(r = -thickness) 
            square(size, center);
    }
}

square_frame([10, 5], 1);

這會產生的結果是…

children 子模組

接下來如果要做一個三角形的框呢?接下來如果要做一個星星的框呢?接下來如果要做個愛心的框呢?你應該都會吧!

如果觀察以上這些需求,會發現都使用了 differenceoffset 模組與 thickness 參數,其他各自不同的參數是給實際的 circlesquare 等模組使用,那麼將相同的程式碼抽取出來,不同的部份使用 children 取代呢?

module frame(thickness) {
    difference() {
        children();
        offset(r = -thickness) 
            children();
    }
}

frame(1) 
    circle(3, $fn = 48);
frame(1) 
    square([10, 5]);

frame 中,children 表示這邊要操作一個子模組,實際上是哪個並不知道,真正的子模組會在呼叫 frame 時指定,執行的結果如下:

children 子模組

儘管形式上不太相同,children 的行為視實際指定的子模組來決定,確實展現了一種多型(polymorphism)。

在〈hull 繪製凸包〉實作了 polyline2dpolyline3d,兩個模組的實作流程類似,只不過一個使用 circle,另一個使用 sphere,這可以透過 children 來指定:

module polyline_join(points) {
    for(i = [0:len(points) - 2]) {
        hull() {
            translate(points[i])
                children();
            translate(points[i + 1])
                children();
        }
    }
}

linear_extrude(1)
polyline_join([
    [0, 0],
    [10, 0],
    [10, 10],
    [-10, 10],
    [-10, -10],
    [20, -10],
    [20, 20]
]) circle(1);


polyline_join([
    [0, 0, 0],
    [10, 0, 5],
    [10, 10, 10],
    [-10, 10, 15],
    [-10, -10, 20],
    [20, -10, 25],
    [20, 20, 30]
]) sphere(1);

如上示範的,透過單一的 polyline_join,就可以解決繪製線段的問題,而且還可以自行指定連接點的模組,繪製的成果如下:

children 子模組

多個子模組

如果要操作的子模組不只一個,可以使用 $children 確認子模組的數量,透過索引來指定子模組,例如:

module lineup(space) {
    for(i = [0 : $children - 1]) {
        translate([space * i, 0, 0]) 
            children(i);
    }
}

lineup(100) { 
    sphere(10); 
    sphere(10); 
}

lineup(100) { 
    cube(35); 
    cube(35); 
    cube(35); 
}

指定多個子模組時,要使用 {} 括住,以上可以繪製出:

children 子模組

檢驗父模組

OpenSCAD 有個 $parent_modules,在自訂模組中使用時,可以用來得知外層有幾個父模組,呼叫 parent_module(index) 的話,可以取得父模組名稱,包含 $parent_modulesparent_module 的模組,算是一個父模組,parent_module 可指定的索引從 0 開始,例如:

module foo0() {
    echo("====");
    for(i = [1:$parent_modules]) {
        echo(i, parent_module(i - 1));
    }
}

foo0();

module foo1() {
    foo0();
}

foo1();

這會顯示以下的結果:

ECHO: "===="
ECHO: 1, "foo0"
ECHO: "===="
ECHO: 1, "foo0"

父模組的資訊在呼叫 children 的場合也會計算:

module parent() {
    children();
}

module foo() {
    for(i = [1:$parent_modules]) {
        echo(i, parent_module(i - 1));
    }
}

foo();

echo("====");
parent()
    foo();

echo("====");
parent()    
parent()
    foo();

這會在控制台顯示以下結果:

ECHO: "===="
ECHO: 1, "foo"
ECHO: "===="
ECHO: 1, "foo"
ECHO: 2, "parent"
ECHO: "===="
ECHO: 1, "foo"
ECHO: 2, "parent"
ECHO: 3, "parent"w

就程式設計而言,需要知道父模組細節來撰寫程式時,意謂著與父模組間具有相依性,因此會使用到 $parent_modulesparent_module(index) 的場合,可能是你想限定模組只能在某些父模組中使用。例如:

module foo() {
    module _foo() {
        // ... 實作內容
    }
    _foo();
}

foo();

OpenSCAD 的模組中可以再定義模組,在以上的範例中,_foo 模組設計為 foo 內部會呼叫的模組,然而在一個模組中有大量內部模組的定義時,令模組可讀性降低時,你可能會選擇將之移出,為了避免被誤用,可能會選擇這麼實作:

module _foo() {
    assert(
        $parent_modules > 1 && parent_module(1) == "foo", 
        "_foo is an internal module of foo"
    );
    // ... 實作內容
}

module foo() {
    _foo();
}

foo();

在上例中,assert 用於斷言測試,若執行結果為 true,什麼事都不會發生,若為 false 會中斷程式,在控制台顯示以下訊息:

children 子模組

分享到 LinkedIn 分享到 Facebook 分享到 Twitter