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"