跳轉到內容

OpenSCAD 使用者手冊/技巧與竅門

來自 Wikibooks,開放世界的開放書籍

許可證說明

[編輯 | 編輯原始碼]

此頁面上顯示的所有程式碼片段都旨在免費使用,無需任何署名,也不限於任何用途,例如,將此處的所有程式碼貢獻視為放置在公共領域或 CC0 許可證下。 這並不意味著要更改頁面的整體正常許可證和/或手冊本身。

從列表對映值

[編輯 | 編輯原始碼]
// The function that maps input values x to output values, the
// example uses floor() to convert floating point to integer
// values.
function map(x) = floor(x);
  
input = [58.9339, 22.9263, 19.2073, 17.8002, 40.4922, 19.7331, 38.9541, 28.9327, 18.2059, 75.5965];
  
// Use a list comprehension expression to call the map() function
// for every value of the input list and put the result of the
// function in the output list.
output = [ for (x = input) map(x) ];
  
echo(output);
// ECHO: [58, 22, 19, 17, 40, 19, 38, 28, 18, 75]

過濾列表中的值

[編輯 | 編輯原始碼]
// The function that define if the input value x should be
// included in the filtered list, the example selects
// all even values that are greater than 6.
function condition(x) = (x >= 6) && (x % 2 == 0);
  
input = [3, 3.3, 4, 4.1, 4.8, 5, 6, 6.3, 7, 8];
  
// Use a list comprehension expression to call the condition()
// function for every value of the input list and put the value
// in the output list if the function returns true.
output = [ for (x = input) if (condition(x)) x ];
  
echo(output);
// ECHO: [6, 8]

新增列表中的所有值

[編輯 | 編輯原始碼]
// Create a simple recursive function that adds the values of a list of floats;
// the simple tail recursive structure makes it possible to
// internally handle the calculation as loop, preventing a
// stack overflow.
function add(v, i = 0, r = 0) = i < len(v) ? add(v, i + 1, r + v[i]) : r;
 
input = [2, 3, 5, 8, 10, 12];
 
output = add(input);

echo(output);
// ECHO: 40
//------------------ add2 -----------------------
// An even simpler non recursive code version of add explores the 
// the matrix product operator
function add2(v) = [for(p=v) 1]*v;

echo(add2(input));
// ECHO: 40

// add2 works also with lists of vectors
input2 = [ [2, 3] , [5, 8] , [10, 12] ];
echo(add2(input2));
// ECHO: [17, 23]
echo(add(input2));
// ECHO: undef  // Why?
//----------------- add3 --------------------------
// With a little more code, the function add may be used also 
// to add any homogeneous list structure of floats
function add3(v, i = 0, r) = 
    i < len(v) ? 
        i == 0 ?
            add3(v, 1, v[0]) :
            add3(v, i + 1, r + v[i]) :
        r;

input3 = [ [[1], 1] , [[1], 2] , [[1], 3] ];
input4 = [ 10, [[1], 1] , [[1], 2] , [[1], 3] ];

echo(add3(input3));
// ECHO: [[3], 6]
echo(add2(input3));
// ECHO: undef // input3 is not a list of vectors
echo(add3(input4));
// ECHO: undef // input4 is not a homogeneous list

累積和

[編輯 | 編輯原始碼]

[注意: 需要版本2019.05]

//create a cumulative-sum function using a c-style generator
values = [1,2,65,1,4];

cumsum = [ for (a=0, b=values[0]; a < len(values); a= a+1, b=b+values[a]) b];

// Does not cause a warning "WARNING: undefined operation (number + undefined) in file ..."
cumsum2 = [ for (a=0, b=values[0]; a < len(values); a= a+1, b=b+(values[a]==undef?0:values[a])) b];

echo(cumsum);
// ECHO: [1, 3, 68, 69, 73]
echo(cumsum2);
// ECHO: [1, 3, 68, 69, 73]

計算列表中滿足條件的值的數量

[編輯 | 編輯原始碼]
// The function that define if the input value x should be
// included in the filtered list, the example selects
// all even values that are greater than 6.
function condition(x) = (x >= 6) && (x % 2 == 0);
 
input = [3, 3.3, 4, 4.1, 4.8, 5, 6, 6.3, 7, 8];
 
// Use a list comprehension expression to call the condition()
// function for every value of the input list and put the value
// in the output list if the function returns true.
// Finally the count is determined simply by using len() on the
// filtered list.
output = len([ for (x = input) if (condition(x)) x ]);
 
echo(output);
// ECHO: 2

查詢列表中最大值的索引

[編輯 | 編輯原始碼]
// Create a function that find the index of the maximum value
// found in the input list of floats
function index_max(l) = search(max(l), l)[0];

input = [ 6.3, 4, 4.1, 8, 7, 3, 3.3, 4.8, 5, 6];

echo(index_max(input));
// Check it
echo(input[index_max(input)] == max(input));
// ECHO: 3
// ECHO: true

關注 undef

[編輯 | 編輯原始碼]

OpenSCAD 中的大多數非法操作都會返回undef。有些返回nan。但是,程式會繼續執行,如果未採取預防措施,undef值可能會導致不可預測的未來行為。當函式呼叫中缺少函式引數時,在計算函式表示式時會將其分配給undef值。為了避免這種情況,可以為可選函式引數分配預設值。

// add 'a' to each element of list 'L'
function incrementBy(L, a) =  [ for(x=L) x+a ];

//add 'a' to each element of list 'L'; 'a' default is 1 when missing
function incrementByWithDefault(L, a=1) = [ for(x=L) x+a ];

echo(incrementBy= incrementBy([1,2,3],2));
echo(incrementByWithDefault= incrementByWithDefault([1,2,3],2));
echo(incrementBy= incrementBy([1,2,3]));
echo(incrementByWithDefault= incrementByWithDefault([1,2,3]));
// ECHO: incrementBy= [3, 4, 5]
// ECHO: incrementByWithDefault= [3, 4, 5]
// ECHO: incrementBy= [undef, undef, undef]
// ECHO: incrementByWithDefault= [2, 3, 4]

有時預設值取決於呼叫的其他引數,不能像以前那樣設定;條件表示式可以解決此問題

// find the sublist of 'list' with indices from 'from' to 'to' 
function sublist(list, from=0, to) =
    let( end = (to==undef ? len(list)-1 : to) )
    [ for(i=[from:end]) list[i] ];

echo(s0= sublist(["a", "b", "c", "d"]) );  	// from = 0, end = 3
echo(s1= sublist(["a", "b", "c", "d"], 1, 2) ); // from = 1, end = 2
echo(s2= sublist(["a", "b", "c", "d"], 1)); 	// from = 1, end = 3
echo(s3= sublist(["a", "b", "c", "d"], to=2) );	// from = 0, end = 2
// ECHO: s0 = ["a", "b", "c", "d"]
// ECHO: s1 = ["b", "c"] 
// ECHO: s2 = ["b", "c", "d"] 
// ECHO: s3 = ["a", "b", "c"]

from > to時,函式sublist()會返回不希望的值並生成警告(試試看!)。在這種情況下,一個簡單的解決方案是返回空列表[]

// returns an empty list when 'from > to' 
function sublist2(list, from=0, to) =
    from<=to ?
    	let( end = (to==undef ? len(list)-1 : to) )
    	[ for(i=[from:end]) list[i] ] :
	[];

echo(s1= sublist2(["a", "b", "c", "d"], 3, 1));
echo(s2= sublist2(["a", "b", "c", "d"], 1));
echo(s3= sublist2(["a", "b", "c", "d"], to=2));
// ECHO: s1 = []
// ECHO: s2 = []
// ECHO: s3 = ["a", "b", "c"]

上面的輸出s2是空列表,因為to==undef並且fromto的比較結果為falseto的預設值已丟失。要克服這一點,只需反轉測試即可

function sublist3(list, from=0, to) =
    from>to ?
	[] :
    	let( end = to==undef ? len(list)-1 : to )
    	[ for(i=[from:end]) list[i] ] ;

echo(s1=sublist3(["a", "b", "c", "d"], 3, 1));
echo(s2=sublist3(["a", "b", "c", "d"], 1));
echo(s3=sublist3(["a", "b", "c", "d"], to=2));
// ECHO: s1 = []
// ECHO: s2 = ["b", "c", "d"]
// ECHO: s3 = ["a", "b", "c"]

現在,當to未定義時,第一個測試結果為假,並且執行let()。透過仔細選擇測試,我們可以處理undef值。

將圓柱體堆疊在一起

[編輯 | 編輯原始碼]
OpenSCAD - 堆疊圓柱體
// Define the sizes for the cylinders, first value is the
// radius, the second is the height.
// All cylinders are to be stacked above each other (with
// an additional spacing of 1 unit).
sizes = [ [ 26, 3 ], [ 20, 5 ], [ 11, 8 ],  [ 5, 10 ], [ 2, 13 ] ];

// One option to solve this is by using a recursive module
// that creates a new translated coordinate system before
// going into the next level.
module translated_cylinder(size_vector, idx = 0) {
    if (idx < len(size_vector)) {
        radius = size_vector[idx][0];
        height = size_vector[idx][1];

        // Create the cylinder for the current level.
        cylinder(r = radius, h = height);

        // Recursive call generating the next cylinders
        // translated in Z direction based on the height
        // of the current cylinder
        translate([0, 0, height + 1]) {
            translated_cylinder(size_vector, idx + 1);
        }
    }
}

// Call the module to create the stacked cylinders.
translated_cylinder(sizes);

最小旋轉問題

[編輯 | 編輯原始碼]

在二維空間中,除了非常特殊的情況外,只有兩種旋轉可以使向量與另一個向量對齊。在三維空間中,有無限多種。然而,只有一種具有最小的旋轉角度。以下函式構建了該最小旋轉的矩陣。該程式碼是對在 Oskar Linde 的 sweep.scad 中找到的函式的簡化。

// Find the unitary vector with direction v. Fails if v=[0,0,0].
function unit(v) = norm(v)>0 ? v/norm(v) : undef; 
// Find the transpose of a rectangular matrix
function transpose(m) = // m is any rectangular matrix of objects
  [ for(j=[0:len(m[0])-1]) [ for(i=[0:len(m)-1]) m[i][j] ] ];
// The identity matrix with dimension n
function identity(n) = [for(i=[0:n-1]) [for(j=[0:n-1]) i==j ? 1 : 0] ];

// computes the rotation with minimum angle that brings a to b
// the code fails if a and b are opposed to each other
function rotate_from_to(a,b) = 
    let( axis = unit(cross(a,b)) )
    axis*axis >= 0.99 ? 
        transpose([unit(b), axis, cross(axis, unit(b))]) * 
            [unit(a), axis, cross(axis, unit(a))] : 
        identity(3);

在 OpenSCAD 中繪製“線”

[編輯 | 編輯原始碼]
OpenSCAD - 結
// An application of the minimum rotation
// Given two points p0 and p1, draw a thin cylinder with its
// bases at p0 and p1
module line(p0, p1, diameter=1) {
    v = p1-p0;
    translate(p0)
        // rotate the cylinder so its z axis is brought to direction v
        multmatrix(rotate_from_to([0,0,1],v))
            cylinder(d=diameter, h=norm(v), $fn=4);
}
// Generate the polygonal points for the knot path 
knot = [ for(i=[0:2:360])
         [ (19*cos(3*i) + 40)*cos(2*i),
           (19*cos(3*i) + 40)*sin(2*i),
            19*sin(3*i) ] ];
// Draw the polygonal a segment at a time
for(i=[1:len(knot)-1]) 
    line(knot[i-1], knot[i], diameter=5);
// Line drawings with this function is usually excruciatingly lengthy to render
// Use it just in preview mode to debug geometry

旋轉規則幫助中可以找到另一種 line() 模組的方法。

凸包序列或鏈

[編輯 | 編輯原始碼]

使用迴圈,即使使用少量引數,也可以透過迭代生成船體片段來組成複雜的模型。以下示例中的迴圈體被重複評估,使用'i'的值,從i=1開始,每次增加1,直到評估到i=18並在評估到i=19之前終止。這為'i'和'j'的值生成了以下二維陣列:[[1,2], [2,3], [3,4], ..., [16,17], [17,18], [18, 19]]

for (i=[1:18]){
  j=i+1;
  hull(){
    translate([0,0,i])
      cylinder(.1,d1=10*sin(i*9),d2=0);
    translate([0,0,j])
      cylinder(.1,d1=10*sin(j*9),d2=0);
  }
}

將文字適應到給定區域

[編輯 | 編輯原始碼]

目前沒有辦法查詢text()生成的幾何圖形的大小。根據模型,可能可以計算文字大小的粗略估計,並將文字適應到已知區域。這使用resize()來實現,假設長度是主要值。

OpenSCAD - 將文字適應到給定區域
// Generate 2 random values between 10 and 30
r = rands(10, 30, 2);

// Calculate width and length from random values
width = r[1];
length = 3 * r[0];

difference() {
    // Create border
    linear_extrude(2, center = true)
        square([length + 4, width + 4], center = true);
    // Cut the area for the text
    linear_extrude(2)
        square([length + 2, width + 2], center = true);
    // Fit the text into the area based on the length
    color("green")
        linear_extrude(1.5, center = true, convexity = 4)
                resize([length, 0], auto = true)
                    text("Text goes here!", valign = "center", halign = "center");
}

建立映象物件同時保留原始物件

[編輯 | 編輯原始碼]

mirror()模組只會轉換現有的物件,因此它不能用於生成對稱物件。但是,使用children()模組,可以很容易地定義一個新的模組mirror_copy(),除了原始物件之外,還可以生成映象物件。

OpenSCAD - 映象複製
// A custom mirror module that retains the original
// object in addition to the mirrored one.
module mirror_copy(v = [1, 0, 0]) {
    children();
    mirror(v) children();
}

// Define example object.
module object() {
    translate([5, 5, 0]) {
        difference() {
            cube(10);
            cylinder(r = 8, h = 30, center = true);
        }
    }
}

// Call mirror_copy twice, once using the default to
// create a duplicate mirrored on X axis and
// then mirror again on Y axis.
mirror_copy([0, 1, 0])
    mirror_copy()
        object();

在空間陣列上排列部件

[編輯 | 編輯原始碼]

一個用於在陣列上顯示一組物件的運算子。

OpenSCAD - 物件陣列
 // Arrange its children in a regular rectangular array
 //      spacing - the space between children origins
 //      n       - the number of children along x axis
 module arrange(spacing=50, n=5) {
    nparts = $children;
    for(i=[0:1:n-1], j=[0:nparts/n])
        if (i+n*j < nparts)
            translate([spacing*(i+1), spacing*j, 0]) 
                children(i+n*j);
 }

 arrange(spacing=30,n=3) {
    sphere(r=20,$fn=8);
    sphere(r=20,$fn=10);
    cube(30,center=true);
    sphere(r=20,$fn=14);
    sphere(r=20,$fn=16);
    sphere(r=20,$fn=18);
    cylinder(r=15,h=30);
    sphere(r=20,$fn=22);
 }

一個方便的運算子,用於顯示從Thingiverse下載的專案的大量部件。

注意:以下用法失敗

 arrange() for(i=[8:16]) sphere(15, $fn=i);

因為for語句對內部物件執行隱式聯合,只建立一個子物件。

圓角多邊形

[編輯 | 編輯原始碼]

多邊形可以透過偏移運算子以多種形式進行圓角處理。

OpenSCAD 偏移運算子 offset() 對多邊形的圓角處理
 p = [ [0,0], [10,0], [10,10], [5,5], [0,10]];

 polygon(p);
 // round pointed vertices and enlarge
 translate([-15, 0])
 offset(1,$fn=24) polygon(p);
 // round concavities and shrink
 translate([-30, 0])
 offset(-1,$fn=24) polygon(p);
 // round concavities and preserve polygon dimensions
 translate([15, 0])
 offset(-1,$fn=24) offset(1,$fn=24) polygon(p);
 // round pointed vertices and preserve polygon dimensions
 translate([30, 0])
 offset(1,$fn=24) offset(-1,$fn=24) polygon(p);
 // round all vertices and preserve polygon dimensions
 translate([45, 0])
 offset(-1,$fn=24) offset(1,$fn=24) 
 offset(1,$fn=24) offset(-1,$fn=24) polygon(p);

倒角物件

[編輯 | 編輯原始碼]

倒角是多邊形圓角的 3D 對應物。3D 物件沒有 offset() 運算子,但可以使用 minkowski 運算子進行編碼。

OpenSCAD - 倒角物件
 difference(){
     offset_3d(2) offset_3d(-2) // exterior fillets
         offset_3d(-4) offset_3d(4) // interior fillets
             basic_model();
     // hole without fillet
     translate([0,0,10]) 
         cylinder(r=18,h=50);
 }
 // simpler (faster) example of a negative offset
* offset_3d(-4)difference(){
     cube(50,center=true);
     cube(50,center=false);
 }

 module basic_model(){
     cylinder(r=25,h=55,$fn=6);// $fn=6 for faster calculation
     cube([80,80,10], center=true);
 }

 module offset_3d(r=1, size=1000) {
     n = $fn==undef ? 12: $fn;
     if(r==0) children();
     else 
         if( r>0 )
             minkowski(convexity=5){
                 children();
                 sphere(r, $fn=n);
             }
         else {
             size2 = size*[1,1,1];// this will form the positv
             size1 = size2*2;    // this will hold a negative inside
             difference(){
                 cube(size2, center=true);// forms the positiv by substracting the negative
                 minkowski(convexity=5){
                     difference(){
                         cube(size1, center=true);
                         children();
                     }
                     sphere(-r, $fn=n);
                 }
             }
         }
 }

請注意,這是一個非常耗時的過程。minkowski 運算子會向模型新增頂點,因此每個新的 offset_3d 都比前一個花費更長時間。

計算邊界框

[編輯 | 編輯原始碼]

無法使用 OpenSCAD 獲取物件的邊界框限制。但是,可以計算其邊界框體積。其概念很簡單:對模型在每個軸上的投影(1D 集)進行 hull() 操作,並對它們進行 minkowski() 操作。由於無法在 OpenSCAD 中定義 1D 集,因此投影由長度等於投影大小的棍子近似表示。

module bbox() { 

    // a 3D approx. of the children projection on X axis 
    module xProjection() 
        translate([0,1/2,-1/2]) 
            linear_extrude(1) 
                hull() 
                    projection() 
                        rotate([90,0,0]) 
                            linear_extrude(1) 
                                projection() children(); 
  
    // a bounding box with an offset of 1 in all axis
    module bbx()  
        minkowski() { 
            xProjection() children(); // x axis
            rotate(-90)               // y axis
                xProjection() rotate(90) children(); 
            rotate([0,-90,0])         // z axis
                xProjection() rotate([0,90,0]) children(); 
        } 
    
    // offset children() (a cube) by -1 in all axis
    module shrink()
      intersection() {
        translate([ 1, 1, 1]) children();
        translate([-1,-1,-1]) children();
      }

   shrink() bbx() children(); 
}
OpenSCAD - 物件的邊界框

影像顯示了程式碼生成的紅色模型的(透明)邊界框。

module model()
  color("red") 
  union() {
    sphere(10);
    translate([15,10,5]) cube(10);
  }

model();
%bbox() model();

倒角物件技巧的 offset3D 運算子程式碼中的立方體可以很好地替換為物件的邊界框,從而省去人工引數大小。

OpenSCAD - 文字邊界框操作

作為解決此類問題的示例,透過對結果進行少量操作,可以將邊界框用於在任意文本週圍增強特徵,而無需瞭解文字的大小。在此示例中,為文字建立了一個方形底板,並在文字的兩端插入了兩個孔,所有這些都具有固定的邊距。這透過獲取邊界框的投影、均勻擴充套件它、將 y 維度縮小到一個細條並向外擴充套件 x 方向來實現,然後再次減去擴充套件的邊界框投影,留下兩個接近點狀的物件,這些物件可以使用偏移擴充套件成孔。

my_string = "Demo text";

module BasePlate(margin) {
  minkowski() {
    translate(-margin) square(2*margin);
    projection() bbox() linear_extrude(1) children();
  }
}

module TextThing() {
  text(my_string, halign="center", valign="center");
}


hole_size = 3;
margwidth = 2;
linear_extrude(1)
  difference() {
    BasePlate([2*(hole_size+margwidth), margwidth]) TextThing();
    offset(hole_size) {
      difference() {
        scale([1.001, 1])
          resize([-1, 0.001])
          BasePlate([hole_size+margwidth, margwidth]) TextThing();
        BasePlate([hole_size+margwidth, margwidth]) TextThing();
      }
    }
  }

linear_extrude(2) TextThing();

資料高度圖

[編輯 | 編輯原始碼]

內建模組 surface() 能夠建立一個 3D 物件,該物件表示數字矩陣中資料的等高線。但是,surface() 的資料矩陣應儲存在外部文字檔案中。以下模組為使用者程式碼生成的資料集執行 surface() 的精確等高線。

OpenSCAD - 由 surfaceData() 生成的等高線
data = [ for(a=[0:10:360])
          [ for(b=[0:10:360])
              cos(a-b)+4*sin(a+b)+(a+b)/40 ]
       ];

surfaceData(data, center=true);
cube();

// operate like the builtin module surface() but
// from a matrix of floats instead of a text file
module surfaceData(M, center=false, convexity=10){
  n = len(M);
  m = len(M[0]);
  miz  = min([for(Mi=M) min(Mi)]);
  minz = miz<0? miz-1 : -1;
  ctr  = center ? [-(m-1)/2, -(n-1)/2, 0]: [0,0,0];
  points = [ // original data points
             for(i=[0:n-1])for(j=[0:m-1]) [j, i, M[i][j]] +ctr,
             [   0,   0, minz ] + ctr, 
             [ m-1,   0, minz ] + ctr, 
             [ m-1, n-1, minz ] + ctr, 
             [   0, n-1, minz ] + ctr,
             // additional interpolated points at the center of the quads
             // the points bellow with `med` set to 0 are not used by faces
             for(i=[0:n-1])for(j=[0:m-1])
               let( med = i==n-1 || j==m-1 ? 0:
                          (M[i][j]+M[i+1][j]+M[i+1][j+1]+M[i][j+1])/4 )
               [j+0.5, i+0.5, med] + ctr
           ];
  faces = [ // faces connecting data points to interpolated ones
            for(i=[0:n-2])
              for(j=[i*m:i*m+m-2]) 
                each [ [   j+1,     j, j+n*m+4 ], 
                       [     j,   j+m, j+n*m+4 ], 
                       [   j+m, j+m+1, j+n*m+4 ], 
                       [ j+m+1,   j+1, j+n*m+4 ] ] ,
            // lateral and bottom faces
            [ for(i=[0:m-1])           i, n*m+1,   n*m ], 
            [ for(i=[m-1:-1:0]) -m+i+n*m, n*m+3, n*m+2 ], 
            [ for(i=[n-1:-1:0])      i*m,   n*m, n*m+3 ], 
            [ for(i=[0:n-1])     i*m+m-1, n*m+2, n*m+1 ],
            [n*m, n*m+1, n*m+2, n*m+3 ]
        ];
  polyhedron(points, faces, convexity);
}

來自數字字串的整數(十進位制或十六進位制)

[編輯 | 編輯原始碼]

將字串格式的數字轉換為整數,(s2d - 字串到十進位制 - 在我新增十六進位制之前命名為…)

例如 echo(s2d("314159")/100000); // 顯示 ECHO: 3.14159

function s2d(h="0",base=10,i=-1) =
// converts a string of hexa/or/decimal digits into a decimal 
// integers only
	(i == -1)
	? s2d(h,base,i=len(h)-1)
	: 	(i == 0)
		? _chkBase(_d2n(h[0]),base)
		: _chkBase(_d2n(h[i]),base) + base*s2d(h,base,i-1);

function _chkBase(n,b) = 
	(n>=b)
	? (0/0)		// 0/0=nan
	: n;
    

function _d2n(digitStr) =
// SINGLE string Digit 2 Number, decimal (0-9) or hex (0-F) - upper or lower A-F
	(digitStr == undef 
				|| len(digitStr) == undef 
				|| len(digitStr) != 1)
	? (0/0) // 0/0 = nan
	: _d2nV()[search(digitStr,_d2nV(),1,0)[0]][1];

function _d2nV()= 
// Digit 2 Number Vector, use function instead of variable - no footprints
  [	["0",0],["1",1],["2",2],["3",3],["4",4],
		["5",5],["6",6],["7",7],["8",8],["9",9],
		["a",10],["b",11],["c",12],
		["d",13],["e",14],["f",15],
		["A",10],["B",11],["C",12],
		["D",13],["E",14],["F",15]
	];

除錯 Tap 函式

[編輯 | 編輯原始碼]

類似於 Ruby 的 Tap 函式。如果 $_debug 為真,則此函式封裝到控制檯的 echo 副作用並返回物件。

例如,給定 $_debug 為真;x = debugTap(2 * 2, "Solution is: "); // 顯示 ECHO: Solution is: 4

function debugTap(o, s) = let(
  nothing = [ for (i = [1:1]) if ($_debug) echo(str(s, ": ", o)) ]) o;

// usage
// note: parseArgsToString() just concats all the args and returns a pretty str

$_debug = true;

// doubles 'x'
function foo(x) =
  let(
   fnName = "foo",
   args = [x]
 )
 debugTap(x * x, str(fnName, parseArgsToString(args)));

x = 2;
y = foo(x);

echo(str("x: ", x, " y: ", y));

// console display:
// ECHO: "foo(2): 4"
// ECHO: "x: 2 y: 4"
華夏公益教科書