用 OpenSCAD 的人几乎都问过同一个问题:difference() 挖了个孔,按 F5 一看,孔的内壁是绿的,别的地方是黄的。是不是哪里写错了?
没有。这是 OpenSCAD 预览的约定,而且它是故意的。
黄和绿各是什么
默认的配色方案叫 Cornfield。模型的面是黄色 #F9D72C;被减掉的物体留下的面是绿色 #9DCB51。F5 用的是 OpenCSG,一种基于图像的 CSG:屏幕上每个像素最后都归属于某一个图元的表面。一个像素如果落在被 difference() 减掉的那个物体的表面上,就按「背面色」画,于是你看到的孔壁是绿的。
这个约定在调试时很有用。绿面告诉你「这里是减出来的」,黄面告诉你「这里是原来就有的」。一个孔挖穿了没有、两个孔有没有打到一起,看颜色比转着看快得多。
按 F6 渲染,画面变成一水的黄,这也是正常的。老的 CGAL 渲染路径根本不记颜色,所有面都用同一个颜色画。近两年的开发版换了 Manifold 做几何内核,它给每个三角面记着自己来自哪个原始图元,颜色和「被减」这个标记跟着布尔运算一起传下去,所以新版本的 F6 也能看到颜色。
color() 的规则:外层赢
这一条反直觉,但它就是这么定的:
color("red") color("blue") cube(10);这个方块是红的。不是蓝的。
OpenSCAD 从树的根往下走,遇到第一个 color() 就把颜色记下来,往下再遇到的 color() 全部忽略。源码里就一行:if (!state.color().isValid()) state.setColor(node.color);。所以一个库里的 module 自己给零件上了色,你在外面再包一层 color(),你的颜色覆盖它的。反过来想在外层给一组零件统一上色、又想保留某个零件自己的颜色,做不到,只能把它拆出来。
被减掉的物体自己带了 color(),它留下的面显示它自己的颜色,而不是绿色。要让孔壁是蓝的,给那个圆柱 color("blue") 就行。
颜色有三种写法:名字(SVG 那 147 个,"red"、"steelblue"、"lightgray" 都行,大小写不敏感)、十六进制 "#RRGGBB" 或 "#RRGGBBAA"、向量 [r, g, b] 或 [r, g, b, a]。向量的分量是 0 到 1,不是 0 到 255。第四个分量是透明度,color("red", 0.5) 或者 color([1, 0, 0, 0.5]) 都能让零件半透明,预览时能看到它背后的东西。
% 和 #
% 叫背景修饰符。加了 % 的子树不参与渲染:F6 之后它不存在,导出的 STL 里也没有它。F5 预览时它画成半透明的灰色。最常见的用法是在设计一个零件时把它要装上去的东西摆在旁边当参照:
%import("case.stl"); // 外壳,只看不算
translate([12, 8, 3]) cube([30, 20, 2]); // 真正在设计的 PCB 托# 叫调试修饰符。加了 # 的子树照常参与渲染,另外在预览里再叠一层半透明的粉红色。它用来回答「这块到底在哪」。一个 difference() 里减去的圆柱看不见,前面加个 #,它就以粉红色显形,位置对不对一眼就清楚。渲染和导出的时候 # 没有任何作用。
另外两个修饰符 ! 和 * 比较少用:! 只渲染这一棵子树、别的全不要,* 把这棵子树整个禁掉。它们在求值阶段就处理掉了,和颜色无关。
我们的浏览器预览怎么处理这些
Forgent3D 的在线预览跑的是真正的 OpenSCAD,所以上面每一条都照搬:黄面、绿面、外层赢、% 半透明灰、# 半透明粉、$preview 为 true。唯一的区别是预览的几何来自 Manifold 的真实布尔运算,不是 OpenCSG 的图像合成,所以两个物体的面重合时不会出现 F5 那种闪烁。颜色只存在于预览里;导出的 STEP 不带颜色,这一点和桌面版导出 STL 一样。