圖形布林運算

February 25, 2022

在〈OpenSCAD CheatSheet〉可以看到,OpenSCAD 在 2D 與 3D 提供的模組不多,只是 circlesquarespherecube 等基本模組,然而,複雜模型可以從這些基本模型,基於 uniondifferenceintersection 組合而來。

這就是 OpenSCAD 採用的主要建模方式 CSG(Constructive Solid Geometry),也就是一個實體模型,可以表示為立方體、圓柱體、球點等基本物體布林操作後的結果。

相對地,CadQuery 以 OpenCascade 作為核心,而 OpenCascade 採用 BREP(Boundary Representation)來表示實體模型,也就是以點、線、面等作為基礎,其相互間的關係來描述模型。

union 聯集

在模型的布林運算中,union 是最使用的,也就是取模型的聯集,其實你早就在使用了,隨便舉個例子好了:

radius = 10;

$fn = 48;
circle(radius);
translate([0, -radius, 0]) 
    square(radius * 2);

程式碼中建立了一個圓與一個方塊,在 OpenSCAD 裡,圓其實是個正多邊形,這邊使用 OpenSCAD 的特殊變數 $fn 指定這個圓有 48 個邊。

特殊變數是以 $ 開頭的變數,在特定範疇裡定義的特殊變數,該範疇或子範疇內執行的程式碼或呼叫的函式、模組,都可以取得該變數,例如,circle 內部會參考 $fn 來決定邊數,而 $fn 是在全域範疇設定,circle 執行時就可以取得 48 這個值。

特殊變數所以為特殊變數,是因為它並不是函式或模組的參數之一,然而在呼叫函式或模組時,可以在引數列上指定特殊變數,例如,circle(radius, $fn = 48),這麼一來,$fn = 48 就只會套用在該次執行 circle

以上範例接著建立一個方塊,邊長為圓直徑,並在 Y 軸負方向位移一個半徑長,結果就是…

圖形布林運算

如果沒有特別指定,模型最後渲染(render)時也會自動聯集,你也可以使用 union 明確指定:

radius = 10;

union() {
    circle(radius, $fn = 48);
    translate([0, -radius, 0]) 
        square(radius * 2);
}

為了預覽時能更為快速,預覽階段 union 實際上不會執行(因為視覺效果相同),OpenSCAD 稱此為 lazy union。

雖然若沒有特別指定,模型最後渲染時也會自動聯集,然而若模型必須聯集為單一模型,而後進一步布林操作,就得明確使用 union,例如:

intersection() {
    circle(12);
    
    union() {
        translate([-2, 0, 0]) 
            circle(8);
        translate([2, 0, 0])
            circle(8);
    }
}

這會顯示以下的圖形:

圖形布林運算

然而,拿掉 union() 的話,會變成 circle(12)translate([-2, 0, 0]) circle(8) 先進行交集,結果再與 translate([2, 0, 0]) circle(8) 進行交集,呈現的圖形就不同了:

圖形布林運算

平常我不會特別撰寫 union,只有在類似以上的情境才會使用,這是因為 translaterotate 等轉換操作,也會將指定區塊中的模型 union,若在上例將 union() 換為 translate([0, 0])rotate(0),呈現的圖形會相同。

同一個 module 的繪製成果,也會進行 union,例如以下與方才使用 union 的版本,呈現的圖形是相同的:

module foo() {
    translate([-2, 0, 0]) 
        circle(8);
    translate([2, 0, 0])
        circle(8);
}

intersection() {
    circle(12);
    foo();
}

intersection 交集

intersection 是做模型的交集,也就是兩個模型間彼此有重疊的部份,才會保留下來,方才已經看過基本的 intersection 使用方式了。

來看看另一個實際的例子,如何做一個球面字呢?可以使用 text 建立文字後以 linear_extrude 擠出,再與球交集,補上個小球做聯集:

character = "A";
font_size = 10;
thickness = 1;

sphere(font_size);
intersection() {
    sphere(font_size + thickness);
    linear_extrude(font_size * 2) 
        text(character, 
             size = font_size,   // 文字大小
             valign = "center",  // 垂直置中
             halign = "center"   // 水平置中
        );
}

這樣就可以建立以下的 3D 模型了:

圖形布林運算

difference 減集

difference 就是對模型做差集,也就是對模型做減法運算,舉個例子來說,你也許會想在愛心上刻個鏤空字:

radius = 20;
t = "Love";

module heart(radius) {
    half_r = radius / 2;
    little_c_r = half_r * sqrt(2);
    
    circle(radius, $fn = 4);

    translate([half_r, half_r])
    rotate(45)
        circle(little_c_r);
        
    translate([-radius / 2, radius / 2])
    rotate(45)
        circle(little_c_r);
}

difference() {
    heart(radius, $fn = 8);
    translate([0, radius / 2])
        text(t, valign = "center", halign = "center");
}

愛心可以由一個正方形與兩個小圓組成,circle(radius, $fn = 4) 指定了 $fn 為 4,就是為了建立正四邊形,邊是從 [radius, 0] 開始繪製,接著只要將兩個小圓放到正方形邊上的中點就可以了,在這邊將愛心的繪製,封裝為 module 模組,而且可以使用 $fn 指定愛心左右兩個心房的邊數,$fn 必須是 4 的倍數。

小圓的直徑是正四邊形邊長,正四邊形邊長呢?畢式定理對吧!為什麼小圓要旋轉 45 度?為什麼 $fn 必須是 4 的倍數?你可以自己思考一下,範例的繪製結果會是:

圖形布林運算

想用 OpenSCAD 撰寫程式建模,你需要知道規則,有規則才能寫程式,最基本的規則就是有關於幾何的一些基本計算,畢式定理、三角函式之類的,更複雜的模型會需要更複雜的數學,甚至是較複雜的演算法。

這也是我熱愛寫程式建模的原因,為了完成某些模型,就有了動機去搞清楚一些數學或演算法上的東西,這些年來也累積了不少經驗,有些經驗我寫在〈玩轉 p5.js〉,有興趣可以參考。

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