OpenSCAD用户手册/综述

维基教科书,自由的教学读本


简介[编辑]


OpenSCAD是一款2D/3D实体建模程序,它基于函数式编程语言来创建模型并为之提供屏幕预览,又能将其渲染为3D网格,借此可将模型以各种2D/3D文件格式导出。

OpenSCAD用一种脚本来创建2D或3D模型。该脚本实质是一系列动作语句的自由格式列表。

 object();
 variable = value;
 operator()   action();
 operator() { action();    action(); }
 operator()   operator() { action(); action(); }
 operator() { operator()   action();
              operator() { action(); action(); } }
对象

对象(object)是模型的基本组成单元,由2D与3D图元(primitive)创建而成。对象以分号';'结尾。

动作

动作(action)语句包括利用图元创建对象以及为变量赋值。动作语句同样以分号';'作为结尾。

运算符

运算符(operator)或称变换(transformation),用于修改对象的位置、颜色以及其他属性。当运算符的工作域中存在一个以上的动作,就要用大括号'{}'将其圈定起来。多个运算符可执行相同的动作,也可构成一组不同的动作。多个运算符按由右至左的顺序处理,即,离动作最近的运算符率先执行。运算符结尾不加分号';',但是不同的动作间要加分号。

 示例
  
   cube(5);
   x = 4+y;
   rotate(40) square(5,10);
   translate([10,5]) { circle(5); square(4); }
   rotate(60) color("red") { circle(5); square(4); }
   color("blue") { translate([5,3,0]) sphere(5); rotate([45,0,45]) { cylinder(10); cube([5,6,7]); } }

注释[编辑]


注释是一种在脚本或代码中(为你自己或未来研究代码的程序员)撰写备注的方式,可用它来描述代码的工作流程或具体行为。编译器会跳过注释,且用户也不应为逻辑一目了然的代码添加注释。

OpenSCAD采用C++-风格的注释

// 这是一行注释
  
myvar = 10; // 本行的后续部分是一条注释
  
/*
   多行注释
   可跨多行
*/

数值与数据类型[编辑]


OpenSCAD中,一个数值的类型可能是一个数Number (如42), 一个布尔值Boolean (如true), 一个字符串String (如"foo"), 一个范围Range (如[0: 1: 10]), 一个向量Vector (如[1,2,3]), 或未定义值Undefined value (undef)。值可存于变量之中,随之作为参数传入函数,再以函数计算结果返回。

[OpenSCAD是一种有着固定数据类型集的动态类型语言。不存在类型名,也没有用户定义类型。Functions are not values. 事实上,变量与函数各占据着无交集的命名空间。]

[编辑]

数(number)是OpenSCAD中最重要的数值类型,其书写方式与其他语言中常用的十进制表示法相同。例如,-1, 42, 0.5, 2.99792458e+8。[OpenSCAD不支持数的八进制与十六进制表示法。]

除了十进制数以外,下列名称定义的是一些特殊值:

  • PI

OpenSCAD仅支持一种数型,即64位的IEEE浮点数。[OpenSCAD既非将整数与浮点数区分开来,也不支持复数]由于OpenSCAD采用IEEE浮点数标准,因此在数学计算上就会有少量偏差:

  • 我们使用二进制浮点数计算法。对于分数而言,只有分母是2的次方时,才能精确地表示出来。例如,0.2 (2/10)的内部表示并不精准,但0.25 (1/4)与0.125 (1/8)却可以精确表示出来。
  • 可表示的最大数约为1e308。如果计算的数值结果过大,那么将视之为正无穷(打印的反馈结果为inf)。
  • 可表示的最小数约为-1e308。如果计算的数值结果过小,那么将视之为负无穷(打印的反馈结果为-inf)。
  • 如果数值的计算结果无效,那么其结果可能是非数值(打印的反馈结果为nan)
  • 如果一个非零的计算数值结果过于接近0而无法表示,那么当结果为负值时其值为-0,否则其值为0。在一些数学计算中,零(0)与负零(-0)是两种截然不同的数。而且尽管两者比较起来相等,但是打印的反馈结果却不相同。

请注意,尽管能够通过计算数值从打印的反馈结果中看到'inf'与'nan',但是,在OpenSCAD中并不支持这两种数值常量。您可以按以下方式,用这些数值来定义变量:

inf = 1e200 * 1e200;
nan = 0 / 0;
echo(inf,nan);

请注意,'nan'是OpenSCAD中唯一一种不等于其他任意值的值(包括它自己在内)。尽管您可以通过'x == undef'来检测变量'x'是否为未定义值,但是却不能利用'x == 0/0'来检测x是否并非一个数。对此,您必须要通过'x != x'来检测x是否为一个nan。

布林值[编辑]

布林值皆为真值。OpenSCAD中支持两种布林值,分别名为truefalse。 一个布林值能以参数的形式传入条件语句'if()'、条件运算符'? :'以及逻辑运算符'!'(非)、'&&'(与)、'||'(或)。事实上,在上述所有的结构中,您都可以传入任意量值。大多数数值都会转换为布林值下的'true',而将值视作'false'的情况有:

  • false
  • 0 and -0
  • ""
  • []
  • undef

请注意,"false" (此字符串), [0] (一个数值向量), [ [] ] (内含一个空向量的向量), [false] (内含布林值false的向量)与0/0 (并非一个数)都计作true。

字符串[编辑]

一个字符串为0个或多个unicode字符所构成的序列。字符串值常用于在导入文件时确定文件名,以及为调试目的而在使用echo()时显示文本。字符串亦可与2015.03版加入的新式text()图元配合使用。

通过双引号"围起字符序列来编写一个字符串字面值, 就像这样: "" (一个空字符串), 或者"this is a string".

为了将"字符包括在字符串字面值里,要这样写\"。为了将\字符包括在字符串字面值里, 要这样写\\。下列以\开头的转义序列即可用在字符串字面值里:

  • \" → "
  • \\ → \
  • \t → 水平制表符(tab)
  • \n → 换行(newline)
  • \r → 回车(carriage return)
  • \u03a9 → Ω - 关于unicode字符的进一步信息请参见text()

请注意: 这是自OpenSCAD-2011.04版开始新加入的行为。您可以通过下列sed命令来更新旧文件: sed 's/\\/\\\\/g' non-escaped.scad > escaped.scad

 示例:
  
 echo("The quick brown fox \tjumps \"over\" the lazy dog.\rThe quick brown fox.\nThe \\lazy\\ dog.");
  
 结果
ECHO: "The quick brown fox jumps "over" the lazy dog. The quick brown fox. The \lazy\ dog." 舊结果 ECHO: "The quick brown fox \tjumps \"over\" the lazy dog. The quick brown fox.\nThe \\lazy\\ dog."

范围[编辑]

范围(Range)常用于for()循环children()。它们有两种变体形式:

[<start>:<end>]
[<start>:<increment>:<end>]

尽管由方括号[]包裹,它们却并非向量。这是因为它们用冒号:作为分隔符,而非句号。

r1 = [0:10];
r2 = [0.5:2.5:20];
echo(r1); // ECHO: [0: 1: 10]
echo(r2); // ECHO: [0.5: 2.5: 20]

您应当避免采用不能被表示为精确二进制浮点数的步长值(step)。使用整数没有问题,因为其分数中分母部分都为2的次方。例如,0.25 (1/4) 与 0.125 (1/8)都是安全的,但是应避免使用0.2 (2/10)这类值。若使用不能精确表示的步长值,由于计算误差的原因,将导致您所使用的范围内可能存在比预期更多或更少的元素。

若不填写<increment>,其默认值为1。若采用[<start>:<end>]形式表示范围,且<start>值大于<end>值,这将生成一个警告,而其实际逻辑则等价于[<end>: 1: <start>]。若采用[<start>:1:<end>]形式表示范围,且<start>值大于<end>值,这将生成一个警告,而实际逻辑则等价于[]。对于OpenSCAD 2014之后的版本而言,范围中的<increment>可取负值。

未定义值[编辑]

未定义值是一种记作undef的特殊值。它是未被赋值的变量的初始值,常用作传有非法参数的函数或运算所返回的结果。最后要提到的是,可将undef用作一个空(null)值,这等价于其他编程语言中的nullNULL

一切含有undef值的算数表达式的运算结果皆为undef。在逻辑表达式中,undef等价于false。除了undef==undef的计算结果为true以外,其他有undef参与的关系运算符表达的计算结果皆为false

请注意,数值计算也可能会返回'nan' (并非一个数值) 来表示存在非法参数。例如,0/falseundef, 而0/0为'nan'。如果向< 与 >等关系运算符传入非法参数,将返回false。尽管undef是OpenSCAD语言中定义的值,但'nan'却不是。

变量[编辑]


利用带有变量名或标识符的语句来创建OpenSCAD变量, 通过以分号为结尾的表达式来为之赋值。在许多命令式语言中数组所担当的角色,在OpenSCAD中由向量来扮演。

var = 25;
xx = 1.25 * cos(50);
y = 2*xx+var;
logic = true;
MyString = "This is a string";
a_vector = [1,2,3];
rr = a_vector[2];      // 向量中的成员
range1 = [-1.5:0.5:3]; // for()循环范围
xx = [0:5];            // 替代for()循环范围

OpenSCAD是一种函数式编程语言,因此变量都与表达式相绑定,且在整个生命周期中保持为一个恒定的值,这是因为它需要满足引用透明性这项需求。在类似于C这样的命令式编程语言中,上述变量行为就如同常量一般,与其中的普通变量形成鲜明对比。

换言之,OpenSCAD中的变量更像是常量,但仍有一点重要的不同之处。如果多次为变量赋值,则在整个代码中此变量仅存有最后一次所赋的值。这一点可参见进一步的讨论设置变量发生在编译时,而非运行时。此行为的原因是需要通过使用-D variable=value选项,才能在命令行中输入变量。OpenSCAD当前将赋值过程置于源代码的结尾处,这样,一定可使变量的值以上述方式发生改变。

在编译期变量保留其最后一次赋值结果,这与函数式编程语言的行为相符。与C这样的命令式编程语言不同,OpenSCAD并不是一种支持迭代的语言,像x = x + 1这样的语句是不合法的,如果理解了这个概念,您将领略OpenSCAD之美。

2015.03之前的版本

除文件顶层(file top-level)与模块顶层(module top-level)之外,不能在任意作用域内进行赋值。在if/else语句或for循环语句中,需要使用assign()。

自2015.03版开始

可在任何作用域内进行赋值。请注意,只有在定义变量的作用域内对其进行赋值才是有效的——您仍然不能将数值“泄露”到外层的作用域中。参见变量的作用域来获得更多详情。

a=0;
if (a==0) 
  {
 a=1; //  在2015.03版之前,本行将造成一条编译错误
      //  自2015.03版开始,将不再出现错误信息,但是值a=1却被限制在括号{}之内
  }

未定义变量[编辑]

未赋值变量具有一种特殊值undef。 此值可作为条件语句的测试对象,且可由函数作为返回值。

 示例
  
 echo("Variable a is ", a);                // Variable a is undef (变量a为undef)
 if (a==undef) {
   echo("Variable a is tested undefined"); // Variable a is tested undefined (变量a是被测试的未定义变量)
 }

变量的作用域[编辑]

当如translate()与color()这样的运算符需要囊括一个以上的动作时(动作以;结尾), 就要用大括号{}来将动作打包成组,创建一个新的内部作用域。 如果组中仅有一个分号,那么大括号可有可无。

每对大括号都在其所在的作用域中创建了一个新的作用域。自2015.03版开始, 可将新变量创建在此新作用域之内。另外,还可以为外部域中创建的变量指定新值。 这些变量及其值可存在于创建它们的作用域的内部作用域中,但是却不存在于创建它们的作用域的外部域中。变量仍然仅保留作用域中最后一次所赋的值。

                       // 作用域1
 a = 6;                // 创建变量a
 echo(a,b);            //                6, undef
 translate([5,0,0]){   // 作用域1.1
   a= 10;
   b= 16;              // 创建变量b
   echo(a,b);          //              100, 16   a=10;在后面被a=100;覆盖
   color("blue") {     // 作用域1.1.1
     echo(a,b);        //              100, 20
     cube();
     b=20;
   }                   // 返回作用域1,1
   echo(a,b);          //              100, 16
   a=100;              // 在作用域1.1中覆盖a 
 }                     // 返回作用域1   
 echo(a,b);            //                6, undef
 color("red"){         // 作用域1.2
   cube();
   echo(a,b);          //                6, undef
   }                   // 返回作用域1
 echo(a,b);            //                6, undef
  
 // 在本例中,作用域1与作用域1.1都为作用域1.1.1的外部域,而作用域1.2却不是。
不将匿名作用域看作是一种作用域:
 {
   angle = 45;
 }
 rotate(angle) square(10);

对于上述变量规则而言,For()循环也不例外,其中的变量各仅有一个值。OpenSCAD会为每趟循环中的内容创建一个副本。因而每趟循环中都有其自己的作用域,以此令对应循环过程中的变量有其唯一值。所以,您仍然不能使用a=a+1;语句。

设置变量发生在编译时,而非运行时[编辑]

由于OpenSCAD在编译时计算其变量值,而非运行时,因此,只有最后一次变量赋值才会在其所在作用域或其内部域中生效。这样想也许会更有助于理解:即OpenSCAD中的变量是一种可重写(override-able)的常量而非传统意义上的“变量”。

// 'a'的值反映了仅最后一次的赋值才会生效
   a = 0;
   echo(a);  // 5
   a = 3;
   echo(a);  // 5
   a = 5;

上述示例表现出了一种“反直觉”的执行效果,这使您可以做一些有趣的事情:例如,如果您建立了自己的共享库文件,并在其顶层定义了具有默认值的变量,当您将此文件用于自己的项目中时,就可以简单地通过为之赋予新值的方法来“重新定义”或覆盖(override)那些常量。

特殊变量[编辑]

特殊变量提供了另一种向模块或函数传递参数的方式。 所有以一个'$'开头的变量均为特殊变量,这与lisp中的特殊变量很相似。 特殊变量较普通变量而言更为灵活(dynamic动态)。 (对于更多细节可参考其他语言特性)

向量[编辑]


向量是由0或更多的OpenSCAD值按序构成的。因此,向量实为一种由数字、布尔值、变量、向量、字符串或它们之间任意组合而成的集合(或称列表或表)。They can also be expressions which evaluate to one of these. 向量在OpenSCAD中扮演的角色就相当于大多命令式语言中的数组。 The information here also applies to lists and tables which use vectors for their data.

向量的表示是由中括号(方括号)[]围起的0或多个项(或称元素或成员),其间由逗号隔开。向量也能以向量作为元素,而向量元素中还可以包含更多的向量,诸如此类。


示例
   [1,2,3]
   [a,5,b]
   []
   [5.643]
   ["a","b","string"]
   [[1,r],[x,y,z,4,5]]
   [3, 5, [6,7], [[8,9],[10,[11,12],13], c, "string"]
   [4/3, 6*1.5, cos(60)]

在OpenSCAD中使用向量:

  cube( [width,depth,height] );           // 空格可选,这里为方便阅读而设
  translate( [x,y,z] )
  polygon( [ [x0,y0],  [x1,y1],  [x2,y2] ] );
创建向量

创建向量要列出其中元素,以逗号分隔,并用方括号包围起来。变量元素会以它们的实际值来代替。

  cube([10,15,20]);
  a1 = [1,2,3];
  a2 = [4,5];
  a3 = [6,7,8,9];
  b  = [a1,a2,a3];    // [ [1,2,3], [4,5], [6,7,8,9] ] 请注意,这里增加了嵌套的深度(层数)
向量中的元素

向量中的元素从0至n-1进行编号,其中的n是len()函数返回的向量长度值。 通过下列方式来定位向量中的元素:

e[5]           // 元素5 (第6个元素)                           第1层嵌套
e[5][2]        // 元素5中的第2个元素                          第2层嵌套
e[5][2][0]     // 元素5中的第2个元素中的第0个元素             第3层嵌套
e[5][2][0][1]  // 元素5中的第2个元素中的第0个元素的第1个元素  第4层嵌套
利用len()函数得到元素长度的示例
e = [ [1], [], [3,4,5], "string", "x", [[10,11],[12,13,14],[[15,16],[17]]] ];  // e的长度为6

地址          长度    元素
e[0]          1       [1]
e[1]          0       []
e[5]          3       [ [10,11], [12,13,14], [[15,16],[17]] ]
e[5][1]       3       [ 12, 13, 14 ]
e[5][2]       2       [ [15,16], [17] ]
e[5][2][0]    2       [ 15, 16 ]
e[5][2][0][1] undef   16
    
e[3]          6       "string"
e[3][2]      1       "r"
  
s = [2,0,5]; a = 2;
s[a]          undef   5
e[s[a]]       3       [ [10,11], [12,13,14], [[15,16],[17]] ]
另一种点表示法

向量中的前三个元素可用另一种点表示法进行访问:

e.x    // 等价于e[0]
e.y    // 等价于e[1]
e.z    // 等价于e[2]
向量操作符[编辑]

concat[编辑]

[请注意: 需要使用版本 2015.03]

concat()将两个或两个以上向量中的元素合并至单个向量。同时并不改变原向量的嵌套层次。

 vector1 = [1,2,3]; vector2 = [4]; vector3 = [5,6];
 new_vector = concat(vector1, vector2, vector3); // [1,2,3,4,5,6]
  
 string_vector = concat("abc","def");                 // ["abc", "def"]
 one_string = str(string_vector[0],string_vector[1]); // "abcdef"
len[编辑]

len()是一个返回向量或字符串长度的函数。 元素的索引值范围为由[0]至[length-1]。

向量
返回此层级的元素数量。
单值并非向量, 此时将返回undef.
字符串
返回字符串中的字符数量。
 a = [1,2,3]; echo(len(a));   //  3

参见有不同长度的向量元素示例

矩阵[编辑]

向量中的元素为向量是谓矩阵。

定义了一个2D旋转矩阵的示例
mr = [
     [cos(angle), -sin(angle)],
     [sin(angle),  cos(angle)]
    ];

获取输入[编辑]


我们现在已掌握了变量,可以用它方便地获取用户的输入,而不仅从代码中对其进行设置。OpenSCAD中提供了一些函数用于从DXF文件中国读取数据,或者您可以在命令行中通过-D开关来设置变量。

从图纸中获取一点

获取点功能便于您从技术制图中的2D视图内获取一原始点数据。dxf_cross函数将读取并返回某层中您指定的两条线的交点。这意味着必须指定DXF文件中的两条线才能获取相应交点,而并非指定对应的点实体。

OriginPoint = dxf_cross(file="drawing.dxf", layer="SCAD.Origin", 
                        origin=[0, 0], scale=1);

获取标注值

您可以从技术制图中读取其标注。这对于读取旋转角、挤压(extrusion)高度或零件间的空间是很有用处的。在图纸中,所创建的标注并不能反映其标注值,而仅是一种标识符。为读取此值,您要在自己的程序中指定此标识符:

TotalWidth = dxf_dim(file="drawing.dxf", name="TotalWidth",
                        layer="SCAD.Origin", origin=[0, 0], scale=1);

对于这两个函数的更佳示例,可参考Example009,其效果图位于OpenSCAD主页