OpenSCAD 使用者手冊/技巧與竅門
此頁面上顯示的所有程式碼片段都旨在免費使用,無需任何署名,也不限於任何用途,例如,將此處的所有程式碼貢獻視為放置在公共領域或 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
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並且from和to的比較結果為false:to的預設值已丟失。要克服這一點,只需反轉測試即可
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值。

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

// 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()來實現,假設長度是主要值。

// 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(),除了原始物件之外,還可以生成映象物件。

// 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();
一個用於在陣列上顯示一組物件的運算子。

// 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語句對內部物件執行隱式聯合,只建立一個子物件。
多邊形可以透過偏移運算子以多種形式進行圓角處理。

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 運算子進行編碼。

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

影像顯示了程式碼生成的紅色模型的(透明)邊界框。
module model()
color("red")
union() {
sphere(10);
translate([15,10,5]) cube(10);
}
model();
%bbox() model();
倒角物件技巧的 offset3D 運算子程式碼中的立方體可以很好地替換為物件的邊界框,從而省去人工引數大小。

作為解決此類問題的示例,透過對結果進行少量操作,可以將邊界框用於在任意文本週圍增強特徵,而無需瞭解文字的大小。在此示例中,為文字建立了一個方形底板,並在文字的兩端插入了兩個孔,所有這些都具有固定的邊距。這透過獲取邊界框的投影、均勻擴充套件它、將 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() 的精確等高線。

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]
];
類似於 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"