模型的檢視與除錯

March 5, 2022

有時候一些程式碼,只想在預覽階段時執行,這時可以透過 $preview 特殊變數,在預覽階段它的值會是 true,若執行渲染的話值會是 false。例如:

if($preview) {
    ...預覽階段要執行的流程
}

在建立模型的過程中,經常要做許多基本模型的組合,然而,在預覽時左看右看,可能就是看不到想檢視的細節,這時該怎麼辦呢?

裝飾字元

舉個例子來說,在〈圖形布林運算〉看過怎麼建一個愛心:

radius = 20;

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);
}

heart(radius, $fn = 8);

這個模型是由一個邊數為四的正多邊形與兩個圓形組合而成,如果想分別檢視正方形或圓形,可以在模型前放個 # 突顯字元(highlight character),將模型以紅色突顯出來,例如:

radius = 20;

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);
}

heart(radius, $fn = 8);

這樣就會看到以下的畫面:

模型的檢視與除錯

如果想建立一個中空球:

radius = 10;
thickness = 1;

module hollow_sphere(radius, thickness) {
	inner_radius = radius - thickness;

	difference() {
		sphere(radius);
		sphere(inner_radius);
	}   
}

hollow_sphere(radius, thickness);

當然,看不到球的內部,若想檢視內外兩球體的差異,可以使用 % 透明字元(transparent character),被標示的模型會以半透明顯示,例如:

	difference() {
		%sphere(radius);
		sphere(inner_radius);
	}   

這樣就可以看出內外兩個球體了:

模型的檢視與除錯

有時候只想看看模型中某個子模型,例如,在〈圖形布林運算〉談過在球體上放個字母:

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"   // 水平置中
        );
}

如果只想看看其中這段的效果呢?

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

你可能會想註解掉其他程式碼,這是個方式,不過有點麻煩,其實只要在子模型前頭放個 ! 單獨顯示(show only)字元就可以了:

	!linear_extrude(font_size * 2) 
	text(character, 
			size = font_size,   // 文字大小
			valign = "center",  // 垂直置中
			halign = "center"   // 水平置中
	);

這樣就只會看到子模型單獨顯示,就上例來說,就是只顯示被擠出的文字:

模型的檢視與除錯

有時候會想要停用某個子模型,看看結果如何,比方說,想停用方才被擠出的文字,使用註解嗎?也可以,不過,得全部加上註解,像是:

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

或者是這樣:

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

兩種方式都有點麻煩,最方便的方式是使用 * 停用字元(disable character),例如:

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

這樣就可以停用整個子模型的運算了。

「檢視」選單

OpenSCAD 的「檢視」選單有許多選項,其中常用的選項,在模型顯示區下方的工具列就可以使用,隨意地按按看就可以知道各是什麼作用了:

模型的檢視與除錯

上圖還包含了按下工具列最右方「顯示邊緣」按鈕的效果,也就是預覽時將每個面的邊緣也顯示出來。

在 OpenSCAD 中,3D 物件被 difference 後留下的面,預覽時會以綠色表示,例如:

difference() {
    sphere(10);
    translate([10, 0, 0])
        sphere(5);
}

預覽時會顯示以下結果:

模型的檢視與除錯

「檢視」選單中有個「拋出所有視圖」(Thrown Together),這會將參與的 3D 物件全部顯示,若是被拿來 difference 的 3D 物件,會以綠色表示,例如以上範例若「拋出所有視圖」,會顯示以下的結果:

模型的檢視與除錯

如果使用 polyhedron 指定頂點與面索引,「拋出所有視圖」還有個作用,將面的內外以不同顏色表示,預設朝外的面預設會是黃色,朝內的面會是紫色,OpenSCAD 要求朝外的面索引必須是順時針,如果模型是正確的,其實看不到面的內側,因此若看得到紫色的面,表示那個面的索引反了。

例如在〈polygon 與 polyhedron〉繪製了正四面體,如果故意將其中一個面索引改為逆時針:

module tetrahedron(radius) {
    t = (1 + sqrt(5)) / 2;
    tetrahedron_points = radius * [
        [1, 1, 1], 	[-1, -1, 1], [-1, 1, -1], [1, -1, -1]
    ];
    tetrahedron_faces = [
        [2, 1, 0], // 逆時針
		[2, 3, 0], [0, 3, 1], [1, 3, 2]
    ];
    polyhedron(
        points = tetrahedron_points,
        faces = tetrahedron_faces
    );
}

tetrahedron(10);

就會看到出現紫色的面:

模型的檢視與除錯

單一模型的面不對,雖然渲染時 OpenSCAD 不會發生錯誤,然而因為網格(mesh)定義不正確,可能無法匯入其他 3D 軟體,或者需要修復網格定義才能使用,在 OpenSCAD 若試圖將面索引不正確的模型,與其他模型結合運算後渲染,也會引發錯誤,例如:

tetrahedron(10);
sphere(10);

在渲染時會發生「ERROR: The given mesh is not closed! Unable to convert to CGAL_Nef_Polyhedron.」的錯誤。

「檢視」選單的「表面」,可以保留上次渲染結果,若渲染後若修改了程式碼,就會回到預覽狀態,這時若想跟上一個渲染結果比較一下就可以使用。

若有多個模型布林運算,「檢視」選單的「線框」,可以在渲染模型後檢視線框,與預覽狀態時「顯示邊緣」功能不同的地方在於,「顯示邊緣」單純就只是預覽時的估算,沒有模型渲染後的網格資訊,你也只看得到「外側」的邊緣,然而「線框」必須在渲染後才能使用,你可以看到渲染後網格的線框。

例如方才中空球的範例,在渲染後檢視「線框」,可以看到中空球內的線框,因為是 difference 後的面,中空球內的線框以綠色顯示:

模型的檢視與除錯

「檢視」選單的「動畫」,可以在 OpenSCAD 建立動畫,這要搭配 $t 特殊變數,例如:

sphere(10);

rotate($t * 360)
translate([30, 0, 0])
    sphere(5);

$t 會是個 0 到 1 的數字,數字怎麼變化的話,要視執行「檢視」選單的「動畫」後顯示的工具列設定而定:

模型的檢視與除錯

工具列上只要設定兩個部份「FPS」與「步驟」,前者是每秒影格數(frame per second),後者用來設定每個影格間繪製時的 $t 遞增「1 / 步驟」,例如,FPS 設為 1 的話,每秒重新執行一次程式,「步驟」設為 30,表示每次 $t 遞增 1/30,若超過 1,回到 0 重新遞增,工具列右邊的「時間」在設置「FPS」與「步驟」後,就會開始變化。

想繪製動畫的話,程式碼中要做的,就是將 $t 對應至參數變化,就上圖的設定而言,可以看到以下的動畫效果:

模型的檢視與除錯

動畫工具列的最右方有個「轉存圖片」,在動畫過程中若選取,它會將 $t 從 0 到 1 變化的過程中繪製的畫面,使用圖片檔存下來。

預覽破圖

有些模型預覽會產生破圖,例如:

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

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

這會產生以下的預覽:

模型的檢視與除錯

預覽終究是預覽,沒有渲染後完整的網格資訊才會如此,你可以把上面的預覽想成,OpenSCAD 仍然以文字擠出時的凸面資訊,來計算與球面交集後預覽,結果就是有些面無法正確地預覽。

OpenSCAD 有些模組可以設置 convexity 參數,通常是建立後的模型可能有凹凸面的模組,如果模組有 convexity 參數可以使用,可以試著設為較大的值,例如,將上例的 linear_extrude(font_size * 2) 改為 linear_extrude(font_size * 2, convexity = 10),就可以正確地預覽:

模型的檢視與除錯

OpenSCAD 建議可以由 10 開始設置,較高的 convexity 會對預覽造成負擔,然而也只會影響預覽,對渲染沒有影響。

如果 convexity 沒辦法解決預覽時的破圖問題,那就只能渲染了,雖然可以按下 F6 執行渲染,不過這會渲染整個模型,若只是某個子模型破圖,可以在該模組前加上 render(),只渲染該模組。例如:

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

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

// 以下還有其他模型的建立
...

echo、assert

OpenSCAD 沒有除錯器(debugger),若想檢視變數值,就只有最傳統的作法,依賴 echo 將變數值顯示出來,可以指定多個值顯示,並以逗號區隔,例如:

x = 10;
y = 10;
echo(x, y);         // 顯示 ECHO: 10, 20
echo(x = x, y = y); // 顯示 ECHO: x = 10, y = 20

可以看到 echo 裡還可以有 var = value 的指定方式,這對於檢視變數與值的對應有幫助;echo 也可以是個運算式,這是為了能在 function 裡使用,這時只會將後續指定的值傳回,例如:

function foo(a, b) = 
  let(
     x = echo(a = a) a,
     y = echo(b = b) b
  )
  x + y;

/*
ECHO: a = 1
ECHO: b = 2
*/
r = foo(1, 2); 

有時你會想要進行斷言,也就是斷定程式到某個流程時,必然是某個狀態,例如變數必須是某個值,這時可以使用 assert

module foo(r){
    assert(r > 0, "r should be greater than 0");
    ...
}

foo(0); // ERROR: Assertion '(r > 0)' failed: "r should be greater than 0" in file test.scad, line 2 

assert 也可以是個運算式,這是為了能在函式裡使用:

function foo(r) = 
    assert(r > 0, "r should be greater than 0") (2 * PI * r / 1234);
    
a = foo(0);  // ERROR: Assertion '(r > 0)' failed: "r should be greater than 0" in file test.scad, line 2 

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