跳至內容

OpenSCAD用戶手冊/要訣與技巧

維基教科書,自由的教學讀本

關於授權

[編輯]

本頁所有的 代碼片段 都可以自由使用,不屬於任何人。換言之,你可以將本頁的代碼片段視為處於公共領域之中或視為以 CC0 協議分發。 這並不意味着整個頁面和/或手冊本身的正常許可也會改變。

處理數據

[編輯]

遍歷列表,生成新的列表

[編輯]
// 定义遍历函数。
// 本例将使用 floor() 函数将浮点数转换为整数。
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];
  
// 使用列表推导式对 input 列表中的每个元素调用 map() 函数,
// 将 map() 函数的输出放入 output 列表之中。
output = [ for (x = input) map(x) ];
  
echo(output);
// ECHO: [58, 22, 19, 17, 40, 19, 38, 28, 18, 75]

過濾列表元素

[編輯]
// 定义过滤条件。
// 本例将保留所有大于 6 的偶数值。
function condition(x) = (x >= 6) && (x % 2 == 0);
  
input = [3, 3.3, 4, 4.1, 4.8, 5, 6, 6.3, 7, 8];
  
// 使用列表推导式对 input 列表中的每个元素调用 condition() 函数,
// 若 condition() 函数返回 true,则将该元素放入 output 列表中。
output = [ for (x = input) if (condition(x)) x ];
  
echo(output);
// ECHO: [6, 8]

將列表中所有元素加在一起

[編輯]
// 新建一个简单的递归函数用于将一个浮点数列表中的所有元素加在一起;
// 简单的尾递归结构使得内部处理时用循环实现成为可能,避免了可能的栈溢出错误。
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 -----------------------
// 更简单的非递归版本,使用矩阵内积运算。
function add2(v) = [for(p=v) 1]*v;

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

// add2 也能用于向量。
input2 = [ [2, 3] , [5, 8] , [10, 12] ];
echo(add2(input2));
// ECHO: [17, 23]
echo(add(input2));
// ECHO: undef  // Why?
//----------------- add3 --------------------------
// 这一版本的代码多了几行,可以用于任意结构相同的嵌套列表的求和。
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

Cumulative sum

[編輯]

[請注意: 需要使用版本 2019.05]

// 使用 C 风格的生成器求累积和
values = [1,2,65,1,4];

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

// 这一版本不会产生形如 "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]

計算列表中符合條件的元質數量

[編輯]
// 定义条件函数。
// 本例将对列表中所有大于 6 的偶数值进行计数。
function condition(x) = (x >= 6) && (x % 2 == 0);
 
input = [3, 3.3, 4, 4.1, 4.8, 5, 6, 6.3, 7, 8];
 
// 使用列表推导式对 input 列表中的每个元素调用 condition() 函数,
// 若 condition() 函数返回 true,则将该元素放入 output 列表中。
// 最终使用 len() 函数得到符合条件的元素的数量。
output = len([ for (x = input) if (condition(x)) x ]);
 
echo(output);
// ECHO: 2

找到列表中最大元素的下標

[編輯]
// 新建一个寻找浮点数列表中最大元素下标的函数
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。為了避免這種情況,定義函數時可以給可選參數設定默認值。

// 给列表 L 中的每个元素加上 a
function incrementBy(L, a) =  [ for(x=L) x+a ];

// 给列表 L 中的每个元素加上 a;如果没有指明 a,就取默认值 1
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==undeffromto 的比較返回 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==undef 沒有定義,from > to 就會返回 false,開始執行以 let() 開頭的代碼。只要仔細選取測試用例,我們就能妥善處理 undef 值。

集合

[編輯]

將圓柱疊在一起

[編輯]
OpenSCAD - Stacked Cylinders
// 定义圆柱的尺寸,前一个值是半径,后一个值是高度。
// 这些圆柱将会叠在一起(中间间隔 1 单位)。
sizes = [ [ 26, 3 ], [ 20, 5 ], [ 11, 8 ],  [ 5, 10 ], [ 2, 13 ] ];

// 解决这一问题的办法之一是使用递归模块,在进入下一层之前处理坐标系的平移
module translated_cylinder(size_vector, idx = 0) {
    if (idx < len(size_vector)) {
        radius = size_vector[idx][0];
        height = size_vector[idx][1];

        // 在本层创建圆柱体
        cylinder(r = radius, h = height);

        // 递归调用以在较本层更高的位置创建下一层的圆柱体
        translate([0, 0, height + 1]) {
            translated_cylinder(size_vector, idx + 1);
        }
    }
}

// 调用模组以创建对叠的圆柱体
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 - Knot
// An application of the minimum rotation
// Given to 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

Another approach to the module line() is found in Rotation rule help.

hull sequence or chain

[編輯]

With a loop hull segments can be chained and so changing objects parametern while lofting.

for (i=[0:20]){
  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 - Fitting text into a given area
// 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");
}

在保留原始物體的同時創建鏡像

[編輯]

The mirror() module just transforms the existing object, so it can't be used to generate symmetrical objects. However using the children() module, it's easily possible define a new module mirror_copy() that generates the mirrored object in addition to the original one.

OpenSCAD - Mirror Copy
// 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();

在一個空間陣列上排列零件

[編輯]

An operator to display a set of objects on an array.

OpenSCAD - An array of objects
 // 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);
 }

A handy operator to display a lot of parts of a project downloaded from Thingiverse.

Note: the following usage fails:

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

because the for statement do an implicit union of the inside objects creating only one child.

多邊形倒圓角

[編輯]

Polygons may be rounded by the offset operator in several forms.

The roundings of a polygon by OpenSCAD operator 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);

切削對象

[編輯]

Filleting is the 3D counterpart of the rounding of polygons. There is no offset() operators for 3D objects, but it may be coded using minkowski operator.

OpenSCAD - Filleting an object
 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);
                 }
             }
         }
 }

Note that this is a very time consuming process. The minkowski operator adds vertices to the model so each new offset_3d takes longer than the previous one.

計算包圍盒

[編輯]

There is no way to get the bounding box limits of an object with OpenSCAD codes. However, it is possible to compute its bounding box volume. Its concept is simple: hull() the projection of the model on each axis (1D sets) and minkowski() them. As there is no way to define a 1D set in OpenSCAD, the projections are approximated by a stick whose length is the size of the projection.

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 - The bounding box of an object

The image shows the (transparent) bounding box of a red model generated by the code:

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

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

The cubes in the offset3D operator code of the Filleting objects tip could well be replaced by the object bounding box dispensing the artificial argument size.

OpenSCAD - Text bounding box manipulation

As an example of solving problems with this, with a little manipulation of the result, the bounding box can be used to augment features around arbitrary text without knowing the size of the text. In this example a square base plate for the text is created with two holes inserted into it at the ends of the text, all having fixed margins. This works by taking the projection of the bounding box, expanding it evenly, shrinking the y dimension to a sliver, and extending the x direction outward by a sliver, and subtracting off the expanded bounding box projection again, leaving two near point-like objects which can be expanded with offset into the holes.

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

數據高度分佈圖

[編輯]

The builtin module surface() is able to create a 3D object that represents the heightmap of data in a matrix of numbers. However, the data matrix for surface() should be stored in an external text file. The following module does the exact heightmap of surface() for a data set generated by the user code.

OpenSCAD - A heightmap generated by 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 意思是 String 2 Decimal,取名的時候還沒有加入十六進制轉換功能……) Converts number in string format to an integer,

例: echo(s2d("314159")/100000); // shows 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 函數。若 $_debugtrue ,則在返回該對象的同時將對象打印到控制台上。

例:當 $_debugtrue 時,x = debugTap(2 * 2, "Solution is: "); 將在控制台打印 Solution is: 4

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

// 使用方法
// note: parseArgsToString() 将所有的参数连接在一起,返回一个漂亮的字符串

$_debug = true;

// 将 '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));

// 控制台将显示:
// ECHO: "foo(2): 4"
// ECHO: "x: 2 y: 4"