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==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==undef
沒有定義,from > to
就會返回 false
,開始執行以 let()
開頭的代碼。只要仔細選取測試用例,我們就能妥善處理 undef
值。
集合
編輯將圓柱疊在一起
編輯// 定义圆柱的尺寸,前一个值是半径,后一个值是高度。
// 这些圆柱将会叠在一起(中间间隔 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 中畫「線」
編輯// 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() 實現這一功能,並假設長度是主導值。
// 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.
// 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.
// 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.
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.
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();
}
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.
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.
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 函數。若 $_debug
為 true
,則在返回該對象的同時將對象打印到控制台上。
例:當 $_debug
為 true
時,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"