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"