OpenSCAD-编程指南-全-
OpenSCAD 编程指南(全)
原文:
zh.annas-archive.org/md5/4c66e4178a171e7cf0d15e85f4996fa4译者:飞龙
前言

OpenSCAD 编程:3D 可打印物体的初学者指南 介绍了功能强大的基于文本的 OpenSCAD 3D CAD 软件。本书指导读者通过使用算术、变量、循环、模块和决策来设计一系列越来越复杂的 3D 设计,这些设计都适合 3D 打印。
什么是 OpenSCAD?
OpenSCAD(发音为Open-S-CAD)是一个基于文本的软件,用于创建实体 3D 模型。它允许你通过编写代码来设计这些模型,这使得你(作为设计师)可以完全控制建模过程,并能够在整个过程中轻松修改设计的任何部分。OpenSCAD 还使得创建由可配置参数定义的设计变得容易,这意味着你可以在设计时考虑到未来的变化。
OpenSCAD 是一种描述性编程语言:其编码语句描述了你整体设计中每个组件的形状、大小和组合。OpenSCAD 提供了两种主要的 3D 建模技术:你可以通过组合简单的形状(称为构造实体几何)来创建复杂的物体,或者将 2D .dxf 或 .svg 轮廓挤压成 3D 形状。与大多数其他用于创建 3D 模型的免费软件(如 Blender)不同,OpenSCAD 更专注于 3D 设计的机械方面,而非艺术方面。因此,当你计划创建可 3D 打印的模型时,OpenSCAD 是合适的工具,但如果你更感兴趣的是制作计算机动画电影,它可能不是你所寻找的工具。
OpenSCAD 是一款免费的开源软件,可供 Windows、macOS 和 Linux 等系统下载。具体的系统要求可以在 openscad.org/downloads 查看。本书覆盖的是 OpenSCAD 2021.01,这是写作时的最新版本。
本书适合的人群
本书适合那些初次接触编程、3D 设计或 OpenSCAD 的初学者。虽然阅读本书所涉及的内容并不要求你具备编程或 3D 设计的背景,但拥有一些初级代数经验会有所帮助。无需掌握基本的数学知识(加法、减法、乘法和除法)。然而,如果你对在简单方程中使用变量有所了解,或者有一些在 xy 平面上绘制点的经验,将会是一个有用的起点。
本书旨在面向初学者,因此我们故意选择专注于 OpenSCAD 的一个子集。我们的目标是以一种易于理解的方式介绍其最有用的元素。在某些情况下,这意味着我们在本书较早的部分简要介绍某个主题,然后在后续章节中更深入地讲解。这样的螺旋式学习方式旨在帮助你在增加更多细节之前,先打下坚实的基础。我们的示例和项目经过精心挑选,既能提供最大限度的创意实用性,又能让编程新手能够轻松理解。
为什么要通过 OpenSCAD 学习编程?
虽然学习编程可能既有趣又令人兴奋,但对于初学者来说,弄清楚编程项目中不可避免的错误的出错原因和错误所在,也可能是一个挑战。与其他基于文本的编程语言(这些语言往往难以看清幕后发生了什么)相比,OpenSCAD 的可视化特性能立即为用户提供有关特定方法正确性的反馈。
编写基于文本的代码来创建 3D 物体是一种强大而有效的学习方式,可以帮助你学习如何组织长序列的编码语句。与更为熟悉的命令式编程语言(如 JavaScript、Python 等)类似,OpenSCAD 包括变量和常见的控制结构(如if语句和循环),并支持创建程序员定义的模块和库。此外,OpenSCAD 使用常见的语法元素,如使用花括号({ })和分号(;)来组织语句,以及熟悉的算术运算符和约定。OpenSCAD 不仅开启了基于文本的 3D 设计世界,还教授了可转移到许多其他流行编程语言的技能。
使用 OpenSCAD 学习编程也提供了独特的优势,有助于培养计算思维。这种计算机特定的方法利用分解、抽象、模式和算法来解决问题,使计算机能够轻松执行解决方案。对于其他编程语言的初学者来说,培养计算思维的直觉可能比较困难,但 OpenSCAD 通过字面上成形的算法和代码语句,使其变得简单。应用抽象和模式意味着在设计中可视化地识别出重复和可预测的元素;分解则是将一个复杂的设计拆分成定义明确的较小部分,而算法自然地从创建一系列步骤开始,这些步骤需要按顺序进行,以创建一个设计。从将 OpenSCAD 设计转化为实际 3D 打印物体中获得的触觉反馈,为学习编程增添了全新的维度。
STEM(科学、技术、工程、数学)和STEAM(在其中加入艺术)是最近流行的两个缩写,描述的是这些传统上被分隔的学科之间交集处的学习活动。使用 OpenSCAD 学习编程就像是采取一种全面的、基于 STEAM 的编程学习方法。OpenSCAD 编程项目要求将视觉形状转换为简洁的文字描述,反之亦然。从手绘草图开始的设计被转换为数学坐标表示,特征通过比例估算进行设计。使用 OpenSCAD 代码进行设计需要在 3D 物体的正交视图和透视视图之间进行导航,并在二维的阴影形式下思考 3D 形状。3D 打印 OpenSCAD 设计通过要求考虑物理公差和调整机器设置来培养工程技能。按照真正的 STEAM 方法,本书要求你在学习 OpenSCAD 编程的过程中,同时发展、结合并练习通常归属于技术、工程、艺术和数学的独立学科的技能。
使用 OpenSCAD 学习编程有许多优点:
-
OpenSCAD 是流行的、免费的开源软件。
-
OpenSCAD 易于学习,使用常见且可转移的文本基础语法,与其他流行编程语言共享。
-
使用 OpenSCAD 设计 3D 物体保留了可发现的设计历史。与其他 3D 设计软件不同,在这些软件中,点击“撤销”会删除一步操作,但在 OpenSCAD 中,你可以轻松地修改设计过程中的早期步骤,而不会抹去后面的步骤。
-
基于文本的 OpenSCAD 文件(.scad)紧凑的大小使得共享、存储和修改 OpenSCAD 模型比使用典型的 3D 建模文件格式更快、更高效。
-
OpenSCAD 有一个易于找到的控制台窗口,可以提供即时且便捷的调试反馈。
-
OpenSCAD 编程项目是可 3D 打印的。
-
OpenSCAD 是视觉学习者的有效第一编程语言选择。
-
使用 OpenSCAD 学习编程在跨学科的、基于 STEAM 的背景下,不仅建立了计算思维的基础,还强化了空间和数学推理能力。
3D 打印与 OpenSCAD
大多数人使用 OpenSCAD 来创建 3D 打印设计。从本质上讲,3D 打印是将虚拟模型转化为实际物理对象的工具。在创建用于 3D 打印的零件时,OpenSCAD 是一个非常好的软件选择。然而,拥有 3D 打印机并不是本书学习或使用 OpenSCAD 的前提条件。我们当然理解看到和触摸自己 3D 设计的吸引力,因此在本书中,我们穿插了一些 3D 打印小贴士,预期许多读者会希望在现实世界中与自己的虚拟设计进行互动。
3D 打印技术被应用于越来越多的领域:机械工程、产品设计、动画、建筑、雕塑、医学、地质学、火箭学等等。3D 打印最初因其在快速原型制作中的应用而流行,设计师能够比以往更快地创建物理模型并获得现实世界的反馈。然而,除了原型设计,3D 打印技术已经发展到可以直接制造各种材料的产品。设计师现在可以使用 3D 打印技术,利用多种塑料、玻璃、金属、磁铁、水泥、瓷器、生物材料,甚至是可食用的食品来构建设计的最终版本!事实上,机械工程师 3D 打印金属火箭发动机部件、牙医 3D 打印瓷质牙科植入物、建筑师 3D 打印水泥住宅,或者雕塑家和珠宝设计师 3D 打印失蜡铸造的蜡模已不再是什么新鲜事。
尽管存在许多类型的 3D 打印技术,但熔融沉积建模仍然是最便宜且最容易获得的技术。本书中的大多数 3D 打印技巧最适合熔融沉积建模,该技术通过将一层层塑料熔化并依次叠加来构建 3D 形状。
本书内容
本书分为三个部分:
-
第 1 到第三章介绍如何绘制和组合基本的 3D 和 2D 形状。
-
第 4 到第六章介绍循环、模块和决策,以便你可以为设计过程添加新的效率层次。
-
第七章作为案例研究,强化之前的主题并介绍与计算思维紧密配合的更高阶设计技能。
本书的前六章伴随着一系列设计时间挑战。这些练习提供了快速复制的设计,适合每章内容的范围。每章最后都有一小部分大项目。这些项目需要比设计时间活动更多的时间和精力,故意选择这些项目以呈现一个渐进式的挑战。
设计时间和大项目部分的设计没有绝对坐标,因为它们旨在激发你构建一个大致相似的模型,而不需要过多关注细节。在这些练习中,像比例性和形状组合这样的主要细节比其他任何事情都重要。所有的设计时间和大项目练习都非常适合 3D 打印。
以下列表列出了每章中介绍的主题:
第一章:使用 OpenSCAD 进行 3D 绘图
介绍了 OpenSCAD 的界面,并教你绘制和放置一些 OpenSCAD 的基本 3D 形状:长方体、球体、圆柱体和圆锥体。OpenSCAD 还可以导入其他应用程序生成的 3D 形状,本章也将介绍这一点。另一个重要的概念是如何通过几种方式组合多个形状。最后,你将学习如何将 OpenSCAD 的 3D 设计导出为 3D 打印文件。本章中的“大项目”旨在帮助你了解 3D 打印机准备软件中的设置。
第二章:更多的形状变换方法
介绍了一些可以应用于第一章中介绍的 3D 形状的附加变换操作。你将学习如何旋转、镜像以及调整 3D 形状的比例。你还将学习更复杂的形状组合方法,包括如何在两个形状之间包裹外壳,以及如何使用minkowski操作将一个形状的属性沿另一个形状的边缘扩展。本章中的 3D 打印技巧介绍了填充和外壳的概念。本章的“大项目”要求你结合第一章和第二章的多个主题,制作你可能实际使用的物品:一个游戏骰子和一个桌面整理器。
第三章:2D 形状
讨论了一种替代的 3D 设计方法——从 2D 阴影构建 3D 形状。你将学习如何使用基本的 OpenSCAD 2D 形状绘制图形,包括圆形、矩形、多边形和文本(包括表情符号)。你还将学习如何通过使用第二章中研究的大多数相同操作来组合这些 2D 形状,以及一种新的 2D 操作叫做偏移。最后,你将看到如何通过沿 z 轴扩展 2D 形状,将它们带入 3D 世界,并使用多种新的操作。章节中的 3D 打印技巧讨论了如何调整 3D 模型的尺寸以适应打印,包括如何将一个大模型拆分成多个部分,从而使你的 3D 打印超出 3D 打印机的构建平台的限制。本章的“大项目”包括讲故事的骰子、一个骰子架和一个由 2D 轮廓构建的 3D 奖杯。
第四章:使用循环和变量
介绍了一个新的计算思维工具:for 循环。你将学习如何使用变量和 for 循环来重复绘制形状。最棒的是,你将学会如何在循环绘制形状时,改变形状的特性(例如大小、位置或旋转)。本章还介绍了注释和控制台打印,这些是规划和调试设计的有用工具。本章中的 3D 打印技巧涉及到一些可能会让你吃惊的小问题,尤其是当你试图从 OpenSCAD 设计中创建 3D 打印对象时:小尺寸特征的局限性、重新配置设计以避免本应分开的部分融合在一起,以及将设计拆分成不同的 .stl 文件以便使用不同颜色的打印线材打印不同的部分。本章的大项目包括一个细节测试、汉诺塔游戏和井字游戏。
第五章:模块
介绍了另一个计算思维工具:将设计分解为多个模块。你将学习如何使用 OpenSCAD 模块创建自己的形状,并使用单独的文件将这些新形状组织成一个可重用(且可共享)的库。你还将创建并使用参数来控制形状的特性,并在模块中定义变量,使得更新新形状的设计变得快速而简便。本章中的大项目包括一个摩天大楼模块和一组新的 LEGO 积木设计库。
第六章:使用 if 语句进行动态设计
介绍了 if 语句,它允许你根据某个条件创建动态变化的设计。你将学习如何使用布尔运算符和逻辑运算符创建各种复杂的条件,以及扩展的 if 语句和 if...else 语句。你将自动化第四章“大项目”中提出的一些设计配置,并结合随机数为你的设计增添趣味和不可预测的元素,使得重复的元素更加有机和自然。本章中的大项目包括创建一个随机森林、一个时钟面盘和一座随机的摩天大楼城市。
第七章:设计大型项目
展示了一个总结性项目,讲解了创建一个大型多文件设计的过程。你将通过使用迭代设计循环,运用计算思维的正式特征,强化并扩展前六章中提出的理念。你将利用 行走框架 方法,将比萨斜塔的简单版本逐步演变成一个与实际塔楼高度相似的 3D 模型。你可以将这个建筑 3D 打印出来,作为对自己在学习过程中所获得知识的奖杯。
如果你在本书中的任何练习中遇到困难,Design Time 和 Big Project 练习的建议解决方案(以及所有章节示例)可以在programmingwithopenscad.github.io/找到。
本书中使用的术语和约定
许多关于编程和计算思维的入门书籍都有,且每位作者都会做出艰难的决策,决定需要为他们所面向的受众提供多少详细信息。由于本书面向初学者,我们选择保持较高的抽象层次,使用通用的词汇和约定。尽管以下一些术语在其他情况下有更精确的定义,但本书的理念是“不要过于纠结于小细节”。
本书中使用以下词汇:
-
形状 任何由 OpenSCAD 创建的二维或三维图形对象。
-
设计 一个 OpenSCAD 创建的内容(即一个 OpenSCAD 程序),通常由多个形状的组合构成。
-
操作 一个 OpenSCAD 命令,用于改变一个或多个形状的外观/属性。
-
参数 指定形状、操作、模块或函数特征的任何值。
-
预览 在屏幕上快速显示设计的过程。
-
渲染 完整评估设计几何图形的过程(并在屏幕上显示)。一旦渲染完成,你可以导出设计。
-
单位 OpenSCAD 中的所有维度都以 单位 进行指定。通常情况下,单位是毫米(根据 3D 打印行业惯例),但 OpenSCAD 本身是无单位的。所有模型应在打印前在 3D 打印准备软件中明确调整尺寸。
-
宽度 与 x 轴相关的维度,在 3D 打印时是“左右”轴。
-
长度 与 y 轴相关的维度,在 3D 打印时是“前后”轴。
-
高度 与 z 轴相关的维度,在 3D 打印时是“上下”轴。
-
2D 形状 具有宽度和长度,但没有高度的形状。
-
3D 形状 具有宽度、长度和高度的形状。
简要介绍使用 OpenSCAD 进行 3D 设计
如果你从未接触过虚拟 3D 模型,通过使用 2D 计算机屏幕来操作本书中创建的 3D 设计可能会感到困惑。理解一些基本概念,尤其是如何在 2D 表面上创造 3D 空间的假象,也可以帮助你顺利过渡到 3D 建模环境。
理解 3D 点
3D 物体有宽度、长度和高度,因此绘制 3D 形状的表示需要使用三个独立的坐标轴:x 轴、y 轴和 z 轴(见图 1)。三个坐标轴的交点叫做原点,在图中表示为点 (0, 0, 0)。每个坐标轴都从原点向正负两个方向延伸。尽管宽度、长度或高度必须是正值,但物体在某一坐标轴上的位置可能会在负方向上(这是相对于原点的位置)。

图 1:3D 坐标系(笛卡尔坐标系。由 Gustavb 使用 PSTricks 创建,依据创作共用 3.0 未移植版许可证授权:commons.wikimedia.org/wiki/File:Cartesian_coordinates_3D.svg)
有时候,2D 屏幕很难准确判断所看到的 3D 点的位置。例如,在图 1 中,点 (3, 0, 5) 也可以解释为点 (0, 4, 3.5)。当不确定某个形状的大小或位置时,可以旋转设计以获得更全面的视角。当你旋转设计时,一个小型的图形图例(在图 2 中用红色圆圈标出)也会相应地旋转,帮助你跟踪每个坐标轴。

图 2:图形图例跟踪坐标轴标签。
这个图例很有用,因为 OpenSCAD 中的坐标轴没有标签。这个图形图例是一个很好的反馈工具,帮助你在旋转视角以理解设计的各个部分时,解释宽度、长度和高度的变化方向。
使用 OpenSCAD 3D 视图工具栏
OpenSCAD 使用多种视角和颜色阴影(与其他 3D CAD 软件一样),以便在 2D 计算机屏幕上表示 3D 形状。除了使用鼠标、触摸屏或触控板旋转设计外,OpenSCAD 3D 视图工具栏(见图 3)提供了多个按钮,可以快速将 3D 视图旋转到正交的 2D 视图,这有助于揭示形状的真实位置和尺寸。

图 3:3D 形状的快速 2D 正交视图
按顺序,按钮会显示以下 2D 视图:右视图、上视图、下视图、左视图、前视图和后视图。
从本书中获得最大收益的最终提示
OpenSCAD 拥有比本书所涉及的更多高级功能和能力。可以将这些章节视为你探索 OpenSCAD 提供的设计可能性的起点。我们还附上了后记,以提供 OpenSCAD 作为一个开源项目发展的背景,并在你读完本书后,提供进一步学习的建议。我们鼓励你查阅openscad.org/上的文档资源,以及本书附录 A 中包含的语言参考,以便深入探索该语言提供的所有可能性。为了快速查看本书前四章涵盖的 OpenSCAD 基本功能,我们还附上了视觉参考资料(附录 B)。
要真正学会如何使用 OpenSCAD 设计和编写 3D 打印物体的代码,你需要定期放下书本。给自己一些机会,键入并修改我们的示例,以及创建你自己的设计时间和大项目练习。然后,将本书作为设计和编写你自己项目的起点。事实上,一旦你学会了新知识,尝试暂时放下书本。重新混合或扩展我们的项目和示例,或者设计一些全新的东西。尝试设计一些有用的东西,能够帮助你将新学的内容应用到你真正感兴趣的项目中。展示并分享你的设计,甚至把你的 3D 打印物品作为礼物送出去。当你真正投入到话题中时,学习新知识会变得更容易,所以最重要的是,享受乐趣!
第一章:OpenSCAD 中的 3D 绘图

本章介绍了 OpenSCAD 3D 设计软件及其内置的编程语言。你将学习如何使用基于文本的命令绘制本书中所有设计的基本 3D 形状,这些形状将作为设计的构建模块。OpenSCAD 易于学习的编程语言,专为 3D 打印设计,是一种描述性语言,比传统程序更自然地描述几何形状。
为什么使用 OpenSCAD?
OpenSCAD是一个开源程序,免费提供下载。它是制造者社区中最广泛使用的 3D 设计软件之一,因此有很多在线资源可供使用。OpenSCAD 的设计目的是让非设计师也能轻松创建 3D 模型。它不像 Photoshop 那样有图形用户界面。相反,你通过基于文本的代码定义设计,这使得移动不同部分、修改设计过程中的早期步骤、与他人共享设计部分、在论坛中讨论设计问题以及通过电子邮件发送设计变得更容易。你可以在 OpenSCAD 中做类似于其他高端工具能做到的事情;然而,OpenSCAD 学习快速、使用简单、更加易于接触。
OpenSCAD 入门
使用 OpenSCAD 创建 3D 设计是一个两步过程。首先,在编辑器窗口中,输入一条代码语句,向 OpenSCAD 提供关于显示内容的指令。图 1-1 显示了一条用于绘制一个简单 OpenSCAD 形状的代码语句,该形状被红圈标出。

图 1-1:编辑器窗口中的立方体代码
这条 OpenSCAD 代码语句有两个部分。第一部分指定你想绘制的形状类型(在本例中是长方体)。第二部分包含所谓的参数,指示该形状的属性。参数允许你指定修改形状外观的值。参数总是放在括号( )内。
接下来,通过点击预览按钮(图 1-2 中的红圈所示)在预览窗口中绘制你的形状,以查看设计的快速视觉预览。

图 1-2:点击预览按钮后绘制立方体
绘制基本的 3D 形状
本节将教你如何编写 OpenSCAD 代码来绘制长方体(立方体或 3D 矩形)、球体和圆柱体,并学习如何从其他设计程序导入形状。
使用 cube 绘制长方体
使用cube命令来创建一个长方体(如图 1-2 所示):
cube([5, 10, 20]);
语句的第一部分,cube,表示你想要绘制一个长方体。括号内的参数通过指定长方体的大小来修改cube命令。方括号([ ])表示一个向量,用于组织长方体的三个维度。向量中数字的顺序很重要:5 是长方体在 x 轴上的宽度,10 是长方体在 y 轴上的长度,20 是长方体在 z 轴上的高度。最后,用分号(;)标记语句的结尾。
请注意,长方体的一个角与原点接触:三个坐标轴交汇的点,表示为坐标(0, 0, 0)。
使用 sphere 绘制球体
要绘制一个球体,使用sphere命令,后面跟着球体半径的括号表示其大小。例如,以下语句绘制了一个半径为 10 单位的球体(见图 1-3):
sphere(10);

图 1-3:一个半径为 10 单位的球体
你可以通过改变球体的半径来改变球体的大小。与长方体不同,长方体可能有三个不同的尺寸:宽度、长度和高度,而球体在三个轴上的尺寸是相同的。这就是为什么基本的sphere命令中括号内只有一个数字。与cube命令一样,用分号标记代码语句的结尾。但与cube命令不同,OpenSCAD 会将球体围绕原点居中。
使用 cylinder 绘制圆柱体和圆锥体
要绘制圆柱体,使用cylinder命令,后面跟着包含圆柱体高度以及构成其顶部和底部的两个圆的半径长度的括号。以下语句绘制了一个两个半径相同的圆柱体(见图 1-4):
cylinder(h=20, r1=5, r2=5);

图 1-4:一个高度为 20 单位,底部半径为 5 单位,顶部半径为 5 单位的圆柱体
因为跟踪圆柱体的三个参数可能会令人困惑,OpenSCAD 允许你为每个参数标注名称,并按任意顺序包含它们。在括号内,设置以下值:h,表示圆柱体沿 z 轴的高度;r1,表示圆柱体底部的半径;r2,表示圆柱体顶部的半径。与sphere和cube命令一样,用分号标记语句的结尾。
圆柱体的两个半径不需要相同。当它们不同的时候,圆柱看起来更像一个顶部被切掉的圆锥(或者,根据数学家的说法,是一个截头圆锥),如图 1-5 所示:
cylinder(h=20, r1=5, r2=3);

图 1-5:一个高度为 20 单位,底部半径为 5 单位,顶部半径为 3 单位的圆锥
你可以通过将其中一个半径设置为0来绘制一个尖顶圆锥,如图 1-6 所示:
cylinder(h=20, r1=0, r2=5);

图 1-6:一个高度为 20 单位,底部半径为 0 单位,顶部半径为 5 单位的尖锥
还要注意,与通过 sphere 和 cube 命令绘制的形状不同,圆柱体是围绕 z 轴居中的,一个面接触 xy 平面。
使用 import 导入 3D 模型
OpenSCAD 允许你从其他 3D 设计程序中导入形状,前提是它们以 .stl 格式保存,这是 3D 模型的常见格式。你可以通过 import 命令导入这些现有的 3D 形状。例如,使用以下语句导入一个名为 3DBenchy.stl 的流行文件(图 1-7):
import("3DBenchy.stl");

图 1-7:一个导入的 3D 模型船只,通常用于校准 3D 打印机
要导入 3D 形状,在 import 命令后将 .stl 文件的名称放在括号内。用引号(" ")将文件名括起来,表示文件名是字面文本,不应被 OpenSCAD 解释。请注意,你应该将 .stl 文件保存在与 OpenSCAD 程序相同的文件夹/目录中,并在生成设计预览之前保存 OpenSCAD 程序,否则 OpenSCAD 可能无法找到该文件。用分号标记语句的结尾。
修改基本形状
在 OpenSCAD 中修改绘制的形状的一些基本方法包括移动或平滑它们。
移动形状
如果你正在创建的设计有多个形状,你需要知道如何在预览窗口中移动这些形状。否则,默认情况下它们会叠加在一起,你可能无法看到不同大小的形状。例如,考虑以下设计(图 1-8):
cube([20, 10, 10]);
sphere(5);
cylinder(h=30, r1=2, r2=2);

图 1-8:多个默认位置绘制的形状
使用 center=true 居中形状
默认情况下,sphere 命令绘制一个以原点为中心的球体;cube、cylinder 和 import 命令则不会这样做。如果你希望绘制其他形状并使它们也围绕原点居中,可以在括号内添加 center=true 参数,如以下代码片段所示(图 1-9):
cube([5, 10, 20], center=true);

图 1-9:一个以原点为中心的长方体
现在,长方体的中心将位于 (0, 0, 0)。你还可以为圆柱体形状添加 center=true 参数,以便将圆柱体和圆锥体围绕原点居中。对于导入的形状,不能使用 center=true 进行居中。
使用 translate 将形状移动到特定位置
要将形状移动到预览窗口中的特定位置,可以使用 translate 操作。此操作会整体修改形状,因此它会被包含在它所要修改的形状之前。
例如,以下语句绘制一个长方体,长方体相对于其默认位置在 x 轴负方向平移了 10 个单位,在 y 轴正方向平移了 20 个单位,z 轴方向平移了 0 个单位(图 1-10):
translate([-10, 20, 0]) cube([20, 10, 10]);

图 1-10:一个平移后的长方体,起始角落位于(–10, 20, 0)
translate操作使用方括号将 x、y 和 z 维度组合成一个向量。类似于指定立方体形状的尺寸,向量中数字的顺序也很重要。平移向量中的第一个数字描述沿 x 轴的移动;第二个数字描述沿 y 轴的移动;第三个数字描述沿 z 轴的移动。最后,用分号标记整个语句的结束。
你可能已经注意到,你用来修改translate操作的向量会移动形状的起始角落——默认情况下,起始角落是与原点相接触的。图 1-11 展示了translate操作如何相对于原点移动长方体(原始立方体以灰色显示)。你可以使用坐标轴图例预测在应用translate操作后形状的位置。

图 1-11:一个在 x 轴上平移了 10 个单位,在 y 轴上平移了 20 个单位的长方体,与位于原点的相同大小的长方体进行比较
要创建更复杂的设计,你可能需要将形状以不同的配置进行移动。在命令前使用translate操作可以将形状移动到不同的位置。例如,以下语句在一个预览窗口中绘制了一个长方体、一个球体和一个圆柱体(图 1-12):
translate([-10, 10, 0]) cube([20, 10, 10]);
translate([20, 0, 0]) sphere(5);
translate([0, 0, -10]) cylinder(h=30, r1=2, r2=2);

图 1-12:三个不同的形状,从默认位置进行平移
球体和圆柱体分别根据它们各自的中心点进行移动,而立方体则相对于与原点接触的角落进行移动。注意,如果你对一个已经居中的立方体和圆柱体应用相同的平移操作,运动是不同的(图 1-13):
translate([-10, 10, 0]) cube([20, 10, 10], center=true);
translate([20, 0, 0]) sphere(5);
translate([0, 0, -10]) cylinder(h=30, r1=2, r2=2, center=true);

图 1-13:三个不同的形状,从中心位置进行平移
使用$fn 平滑曲线
你可能会想知道,为什么到目前为止你画的球体和圆柱体看起来不像是圆的,而是由一系列平面组成的。这是因为 OpenSCAD 和大多数 3D 设计软件一样,使用一组直线来近似曲线。为了节省内存并减少绘制复杂形状所需的处理时间,OpenSCAD 默认使用了相对较少的这些直线。例如,图 1-13 中展示的圆柱体,仅使用了六条线段来近似圆柱体圆形面上的曲线。
要使你的圆柱体和球体更平滑,可以通过包含 $fn 参数来指定用于近似曲线的线段数量。例如,将 $fn 设置为 10 会使圆柱体看起来更圆,因为它用 10 条线段绘制圆柱体的圆周(图 1-14):
cylinder(h=20, r1=2, r2=2, $fn=10);

图 1-14:使用 10 条线段近似圆柱体的曲线
与其他参数一样,在命令中的括号内包含 $fn。
尽管 图 1-14 中的圆柱体比默认圆柱体更圆,但它仍然不够圆。增加 $fn 到更大的值,以使圆柱体更圆(图 1-15):
cylinder(h=20, r1=2, r2=2, $fn=50);
使用 50 个线段时,圆柱体中的曲线看起来更平滑。然而,超过某个点后,增加 $fn 将不再产生明显效果。另外,请注意,OpenSCAD 在生成具有较大 $fn 值的形状时需要更长的时间(因为需要生成更多的细节),因此在设置 $fn 时需要考虑平滑度与计算开销之间的权衡。通常,$fn=50 会产生足够的“圆滑度”。

图 1-15:使用 50 条线段近似的圆柱体曲线
使用布尔操作组合 3D 形状
有时你可能需要创建比目前所做的基本形状更复杂的形状。OpenSCAD 中的 布尔 操作允许你将多个形状(如长方体、球体、圆柱体和圆锥体)组合成一个形状(图 1-16)。你可以通过使用三种操作之一:union、difference 或 intersection 来实现这一点。

图 1-16:基本布尔操作的示意图
union 操作将两个形状合并,difference 操作从一个形状中减去另一个形状,而 intersection 操作只保留两个形状相交的部分。
使用 difference 减去形状
我们从使用 difference 操作减去形状开始(图 1-17):
difference() {
cube([10, 10, 10]);
sphere(5);
}

图 1-17:从长方体中减去一个球体,使用 difference 操作
指定一个 difference 操作,后跟一对括号,然后在一对大括号中输入至少两个命令。使用 difference 操作时顺序很重要;它只保留第一个形状,移除该形状与其他形状相交的部分。在 图 1-18 中注意,如果交换两个形状的顺序,会发生什么情况:
difference() {
sphere(5);
cube([10, 10, 10]);
}

图 1-18:从球体中减去一个长方体,使用 difference 操作
逆转操作会创建一个缺失切片的球体,精确地在 cube 在原始球体上绘制长方体形状的位置。
使用 # 调试差集操作
由于被减去的形状在设计中不再可见,因此很容易失去对其形状的跟踪。为了简化操作,可以在被减去的形状前加上哈希符号(#),以创建该形状的幽灵版本。以下代码与绘制图 1-17 的代码相同,只是它使用哈希符号将球体呈现为幽灵般的图像(图 1-19):
difference() {
cube([10, 10, 10]);
#sphere(5);
}

图 1-19:为帮助解决问题,减去的球体的幽灵版本
使用哈希符号帮助调试设计,然后在设计正确时,确保从代码中删除哈希符号。
使用difference操作避免“闪烁墙壁”
当使用difference操作减去形状时,有时会出现类似图 1-20 中那样的“闪烁墙壁”。

图 1-20:从较大的立方体中减去两个立方体形成闪烁墙壁
闪烁墙壁出现的原因是被减去的形状与它们所减去的形状共享一个面。这会创建一个模糊的情境:应该保留面,还是将其减去?由于这个问题,带有闪烁墙壁的模型无法进行 3D 打印。
为了解决这个问题,只需减去稍微超过外部形状大小的形状(图 1-21)。

图 1-21:从外部立方体中减去两个稍微更大的立方体
一旦你删除了幽灵形状,剩余的形状应不包含任何闪烁墙壁(图 1-22):
difference() {
cube([10, 10, 10]);
translate([-1, 2.5, 2.5]) cube([12, 5, 5]);
translate([2.5, 2.5, -1]) cube([5, 5, 12]);
}

图 1-22:适合 3D 打印的减去形状
现在你应该能够 3D 打印这个设计了。
使用intersection操作雕刻重叠形状
你还可以通过使用intersection操作(图 1-23)雕刻出两个形状的重叠部分以外的所有部分。
intersection() {
sphere(5);
cube([10, 10, 10]);
}

图 1-23:使用intersection操作绘制的重叠球体和立方体的切割图
首先,指明intersection操作,后跟圆括号,然后在大括号之间输入至少两个命令。与difference操作不同,在intersection中,包含形状的顺序无关紧要。
使用union操作组合形状
要将形状组合成一个实体,使用union操作(图 1-24):
union() {
cube([10, 10, 10]);
sphere(5);
}

图 1-24:使用union操作将球体和立方体组合在一起
union操作将大括号内的所有形状合并为一个形状。对大括号之间的所有行进行缩进,使代码可读且易于理解。与intersection和difference类似,union操作无法修改,因此你永远不需要在其圆括号内添加任何信息。
尽管看起来你可以通过简单地将形状画在一起合并它们,但每个形状仍然保持独立实体。当使用 difference 操作时,这可能会成为问题,因为该操作仅从大括号中的第一个形状中进行减法。为避免这个问题,你可以通过使用 union 操作将多个形状组合成一个形状。然后,将这个组合形状作为第一个形状包含在 difference 中。例如,下面的程序使用 union 操作同时从两个形状中减去一个球体(见图 1-25):
difference() {
union() {
cube([10, 10, 10]);
cylinder(h=10, r1=2, r2=2);
}
sphere(5);
}

图 1-25:一个球体从圆柱体中减去并与union结合在一起的立方体
OpenSCAD 首先将立方体和圆柱体组合成一个形状,然后从该新形状中减去球体。如果没有 union 操作,OpenSCAD 将分别从立方体中减去圆柱体和球体(见图 1-26)。

图 1-26:一个球体和一个圆柱体从立方体中减去
一旦你通过 difference、intersection 或 union 创建了一个复杂形状,计算机可以轻松地将其分解为几何原型,以生成你设计的准确 3D 模型。然后,你可以在 3D 打印机上打印这个复杂的 3D 模型,或者将其导入到 3D 虚拟现实程序中。
准备 3D 打印
当你准备将 OpenSCAD 设计发送到另一个应用程序进行 3D 打印时,你需要从 OpenSCAD 导出设计的 .stl 版本。然后,你可以将此文件导入到 3D 打印准备软件中,调整设置,再用 3D 打印机将其转化为物理对象。
要导出设计的 .stl 版本,首先通过点击渲染按钮(在图 1-27 中以红色圈出)渲染你的设计。而预览功能生成的是模型的快速图像,渲染则完全计算出定义模型所需的所有表面。特别复杂的设计需要更多的表面,因此渲染时间可能较慢。

图 1-27:使用渲染按钮渲染设计
最后,选择文件▶导出▶导出为 STL将设计导出为 .stl 文件(见图 1-28)。

图 1-28:将设计导出为 .stl 文件
总结
恭喜!现在你应该能够创建包括立方体、球体和圆柱体在内的任何大小的设计,并将它们绘制在 OpenSCAD 的预览窗口中。你还可以导入 3D 形状、平滑曲线,并将形状移动到 x、y 和 z 轴上的任意位置。最后,你应该知道如何通过分组、减法和切割重叠的形状来创建由基本形状构成的复杂设计。
这里有一些需要记住的重要事项:
-
OpenSCAD 命令的名称描述了你希望绘制的形状类型。
-
命令后跟括号。括号
( )内的信息修改命令。括号内的值被称为参数。你可以把参数看作是描述形状特征的形容词。 -
分号(
;)标志着大多数语句的结束。语句可以包含命令和操作。 -
使用
translate操作来移动预览窗口中的形状。通过改变translate操作的向量参数来指示移动的数量和方向。 -
方括号(
[ ])将数字组合在一起形成一个向量。向量内数字的顺序很重要。 -
布尔操作使用花括号(
{ })将多个形状组合在一起。这些花括号也构成一个完整的 OpenSCAD 语句,并且不需要使用分号来结束语句。 -
括号、方括号和花括号总是成对出现。
-
$fn可以作为参数来改变单个形状的平滑度。你也可以在代码的开始处将$fn设置为一个高值,以便为设计中的每个形状生成平滑曲线。较高的$fn值可能会导致渲染时间变慢。 -
使用缩进来帮助使你的代码更具可读性和易于理解。
-
设计必须在导出为.stl文件之前先进行渲染。
第二章:更多的形状变换方式

本章介绍了一系列变换操作,帮助你在创建复杂形状时拥有更多控制权。你将学习如何旋转、反射和缩放形状;如何将它们与共享的外壳结合;以及如何圆化它们的边缘。这些变换操作将扩展你的建模工具箱,使你能够创造出更加复杂的设计。
OpenSCAD 形状变换
首先,你将学习如何使用三种变换操作:rotate、mirror 和 resize。变换操作是一段紧接在形状之前的代码,用来改变形状的位置、大小或方向。为了说明目的,在本章的示例中,我们包括了一个透明的灰色轮廓,以指示原始的、未变换的形状应该出现的位置。
使用 rotate 旋转形状
默认情况下,OpenSCAD 绘制的形状都是按某种特定方式定向的。例如,它绘制的球形是以 (0, 0, 0) 为中心,立方体的一个角落位于 (0, 0, 0)。但是有时,你可能希望你的形状有不同的方向。
改变形状的默认位置的一种方法是旋转它。要旋转形状,需要指定围绕三个轴的旋转角度,并且这些旋转角度可以是正数也可以是负数,单位是度。
以下代码片段将一个长方体围绕 x 轴旋转 90 度(图 2-1):
rotate([90, 0, 0]) cube([30, 20, 10]);

图 2-1:一个围绕 x 轴旋转 90 度的长方体
首先,写下变换的名称,然后在括号内,给 rotate 提供一个用方括号([ ])括起来的向量,以将三个旋转轴组合在一起。向量中的第一个元素是围绕 x 轴的旋转角度,第二个是围绕 y 轴的旋转角度,第三个是围绕 z 轴的旋转角度。接着,编写你想要旋转的形状的代码。像往常一样,使用分号(;)结束整个语句。
因为你将形状围绕 x 轴旋转了 90 度,所以其在 x 轴上的位置保持不变,而它在 yz 平面上的位置发生了变化。
以下代码片段将相同的长方体围绕 y 轴旋转(图 2-2):
rotate([0, 180, 0]) cube([30, 20, 10]);

图 2-2:一个围绕 y 轴旋转 180 度的长方体
在这种情况下,形状相对于 y 轴的位置保持不变,它在 xz 平面上的位置旋转了 180 度。
你也可以通过一个操作围绕两个轴旋转形状,如下示例所示(图 2-3):
rotate([-90, 0, -90]) cube([30, 20, 10]);

图 2-3:一个围绕 x 轴旋转 90 度,围绕 z 轴旋转 90 度的长方体
这个长方体绕 x 轴和 z 轴进行了旋转。你可以更容易地将这个操作想象成两个独立的变换:一个是绕 x 轴旋转,另一个是绕 z 轴旋转。要使形状在两个方向上逆时针旋转 90 度,设置这两个轴的旋转角度为–90\。
尽管通过只应用一次旋转操作可以实现绕多个轴的旋转,但最好将不同的旋转分成单独的、重复的变换。这是因为有时很难预测哪个旋转会首先应用。考虑一下,当绕 z 轴的旋转在绕 x 轴旋转之前应用时,长方体的位置差异(图 2-4):
rotate([-90, 0, 0]) rotate([0, 0, -90]) cube([30, 20, 10]);

图 2-4:一个绕 z 轴旋转–90 度,然后绕 x 轴旋转–90 度的长方体
按照预期顺序显式地应用多个旋转操作,将导致形状在旋转应用后精确地出现在你想要的位置。
使用镜面反射形状
改变形状的默认位置的另一种方式是通过mirror变换将其反射到一个虚拟的二维平面上。正如你从操作名称中可以预期的那样,mirror会创建一个形状的镜像反射。以下语句将在 yz 平面上反射一个截头圆锥(图 2-5):
mirror([10, 0, 0])
translate([0, 10, 0]) rotate([0, 90, 0]) cylinder(h=10, r1=5, r2=2);

图 2-5:通过向量[10, 0, 0]在 yz 平面上反射的截头圆锥
你传递给mirror的向量包含了定义一个虚拟点的 x、y、z 坐标。OpenSCAD 然后从原点画一条到该点的虚拟线,并使用与该线在原点处垂直的 2D 平面作为镜面或反射平面。
为了更清楚地说明,图 2-6 将“镜面”显示为半透明的平面。

图 2-6:通过向量[10, 0, 0]在 yz 平面上反射的截头圆锥
“镜面”与向量垂直,如绿色所示,连接从(0, 0, 0)到(10, 0, 0)的线。注意,创建这个镜面时并不需要使用 10 作为 x 轴的值;任何非零的 x 轴值都能使镜面操作表现相同,因为你的目标只是指定一个与镜面垂直的向量。镜面平面始终包含原点(0, 0, 0)。实际上,mirror操作的向量参数描述了镜面是如何旋转的。
下一条语句将圆柱体通过 xy 平面反射(图 2-7):
mirror([0, 0, 10]) cylinder(h=10, r1=2, r2=5);

图 2-7:通过向量[0, 0, 10]在 xy 平面上反射的圆锥体
这个示例定义了一个位于(0, 0, 10)的点,从定义的点到原点的直线垂直于 xy 平面。mirror操作特别适用于快速创建涉及对称的复杂形状。在这种情况下使用mirror操作可能会节省时间,因为你只需要设计对象的一半,然后使用mirror创建第二半。
请注意,mirror操作并不会复制形状;它将形状移动到镜像位置。如果你想要一个完全对称的形状,首先创建形状,然后在它前面使用mirror操作重复它。
使用resize缩放形状
resize操作允许你拉伸或缩小单个形状的特定维度。当你调整形状的大小时,你可以指定每个轴上的确切尺寸。例如,通过将球体沿单一轴方向拉伸,你可以将它变成一个椭球体(一个被拉长的球体)。
以下代码片段使用resize将一个半径为 1 的球体缩放成椭球体(图 2-8):
resize([10, 10, 20]) sphere(1, $fn=100);

图 2-8:一个被调整尺寸的球体
在编写形状命令之前,将一个向量传递给resize操作,将球体在 x、y 和 z 轴上的新维度组合起来。与所有变换一样,使用分号结束整个语句。
新的椭球体沿 x 轴、y 轴和 z 轴两侧分别拉伸 5 单位、5 单位和 10 单位。
你也可以使用resize来转换一个基本圆柱体(图 2-9):
resize([10, 5, 20]) cylinder(h=5, r1=5, r2=5);

图 2-9:一个调整尺寸的圆柱体
该语句调整了一个基本圆柱体的尺寸,圆柱体的高度和两个半径为 5 单位,使得变换后的圆柱体沿 x 轴(通过原点)拉伸 10 单位,沿 y 轴(同样通过原点)拉伸 5 单位,沿 z 轴(从原点开始)拉伸 20 单位。
更多合并 3D 形状的方法
在第一章中,你学习了三种布尔操作,它们允许你合并多个 3D 形状:union、difference和intersection。你也可以使用hull和minkowski操作将两个形状合并成一个。
使用hull合并形状
hull操作会在两个形状周围创建一个凸包(或外壳)。为了理解这一点,可以想象将气球紧紧地包围住两个或更多的形状,形成一个单一的形状。例如,以下代码创建了一个包围球体和立方体的气球(图 2-10):
hull() {
translate([10, 0, 0]) sphere(8);
translate([-10, 0, 0]) cube([4, 4, 4], center=true);
}

图 2-10:一个包围小立方体和大球体的凸包
hull操作与第一章中描述的布尔操作语法相同。它可以合并两个或更多的形状,与union操作一样,形状的顺序并不重要。
使用 Minkowski 合并形状
minkowski操作创建了一种Minkowski 和,即它将一个形状的边缘与第二个形状的特征相结合。以下示例将一个球体包裹在圆柱体的边缘,以创建圆角(图 2-11):
$fn=50;
minkowski() {
cylinder(h=15, r1=5, r2=5);
sphere(4);
}

图 2-11:用球体平滑圆柱体的角落
minkowski操作的语法与第一章描述的布尔操作相同。在这个例子中,由于较小的球体已经沿着圆柱体的边缘进行了浮雕处理,圆柱体的边缘变成了圆角。需要注意的是,minkowski操作会生成比原始圆柱体更大的形状,因为将球体包裹在原始圆柱体周围会增加体积。
组合变换
您可以通过将一个操作写在另一个操作前面来组合变换操作。例如,以下代码片段在三个圆柱体上分别先应用rotate操作,再应用translate操作(图 2-12):
translate([5, 0, 0]) rotate([90, 0, 0]) cylinder(h=10, r1=4, r2=4);
translate([5, 0, 0]) rotate([0, 90, 0]) cylinder(h=10, r1=4, r2=4);
translate([5, 0, 0]) rotate([0, 0, 90]) cylinder(h=10, r1=4, r2=4);

图 2-12:三个圆柱体,先旋转后平移
OpenSCAD 首先执行最内层的变换(直接位于形状左侧的操作),然后应用最外层的变换。如果您按相反顺序应用变换,结果将不同。以下代码片段在rotate操作之前应用了translate操作(图 2-13):
rotate([90, 0, 0]) translate([5, 0, 0]) cylinder(h=10, r1=4, r2=4);
rotate([0, 90, 0]) translate([5, 0, 0]) cylinder(h=10, r1=4, r2=4);
rotate([0, 0, 90]) translate([5, 0, 0]) cylinder(h=10, r1=4, r2=4);

图 2-13:三个圆柱体,先平移后旋转
由于 OpenSCAD 按照顺序执行操作,从最接近形状的变换操作开始,您会得到不同的结果。
总结
本章介绍了几种用于变换形状的重要操作。现在,您可以移动、旋转、反射和调整形状的大小。您还可以通过围绕两个形状形成外壳或通过用另一个形状平滑一个形状的角落来组合两个形状。
以下是一些需要记住的重要点:
-
您可以对单个形状和组合形状应用变换操作。
-
使用
union操作组合形状可以减少您在复杂设计中需要应用的变换操作次数。 -
应用一系列
rotate操作通常比将多个旋转合并为一个rotate操作更容易管理。 -
使用
mirror反射组合形状可以节省时间,特别是在构建对称设计时。 -
在应用多个变换操作时,顺序很重要。
-
最接近形状的变换操作会首先被应用。
在下一章中,您将学习如何将 2D 形状转换为 3D 形状,如何对 2D 形状应用变换操作,以及通过组合和操作基本的 2D 形状创建出令人惊讶的复杂 3D 形状。
第三章:二维图形

你现在已经熟悉了一些基本的 OpenSCAD 指令,用于建模简单的三维图形,并且你已经见识过一些可以将这些基本图形转化为更复杂设计的操作。本章将教你如何创建和组合二维图形,以便构建更加复杂的三维设计。
我们将从向你展示如何绘制基本的二维图形开始,然后描述如何在这些基本二维图形的基础上构建复杂的三维设计。使用二维图形将使你能够创建一些使用你目前学到的三维图形和操作无法实现的设计。此外,了解如何创建二维图形在你为其他数字制造技术(如激光切割)设计时也很有用,尽管这超出了本书的范围。
绘制基本二维图形
和三维图形一样,你可以基于一些内置的二维基本图形(称为 circle、 square和 polygon)来构建复杂的二维图形。
使用 circle命令绘制圆形
circle命令允许你通过指定半径来绘制二维圆形,类似于第一章中的 sphere命令。例如,以下语句绘制一个半径为 20 单位的圆形(图 3-1):
circle(20);

图 3-1:渲染的半径为 20 单位的圆形
点击预览按钮将渲染出一个略有深度的圆形(图 3-2)。

图 3-2:预览的半径为 20 单位的圆形
然而,二维图形没有深度。它们只存在于 xy 平面中。要以没有深度的真实形式查看二维图形,请使用渲染按钮。(请注意,在渲染模式下不可能将二维和三维图形混合使用。)由于二维图形没有深度,因此最容易使用工具栏上的顶视图图标来创建二维设计(图 3-3)。

图 3-3:顶视图图标
使用 square绘制矩形
二维 square命令,用于绘制矩形,指定 x 和 y 维度作为一个向量参数。以下语句绘制一个矩形,矩形沿 x 轴延伸 25 个单位,沿 y 轴延伸 10 个单位(图 3-4):
square([25, 10]);

图 3-4:宽度为 25,高度为 10 单位的矩形
使用 square命令表示你想绘制一个矩形,后面跟着一对圆括号。在圆括号内放入方括号,然后在方括号内输入矩形的尺寸,尺寸之间用逗号隔开。这个二维向量只需要 x 和 y 维度,而不像三维向量(x, y, z)那样需要三维立方体的尺寸。向量中的第一个数字表示矩形沿 x 轴的宽度,第二个数字表示矩形沿 y 轴的长度。
记得你需要点击渲染按钮才能将矩形显示为二维图形。
使用 polygon命令绘制多边形
如果你想创建一个 OpenSCAD 中没有内置的二维形状,你可以使用polygon命令来创建你自己的二维形状。
以下语句使用polygon命令绘制一个三角形,顶点分别为[0, 0],[10, 0]和[10, 10](图 3-5):
polygon([ [0, 0], [10, 0], [10, 10] ]);

图 3-5:一个具有三个顶点的三角形
多边形由一组形状的角点(顶点)定义。列表中的每个顶点都是一个包含多边形角点坐标的向量。将每个顶点作为一个向量放在方括号中,然后再在整个顶点列表周围加上一对方括号,将所有顶点组织为向量的向量。
一定要按顺序列出顶点,就像你绕着多边形的边缘行走一样(可以是任意方向)。另外,你不需要重复指定起始点;OpenSCAD 会自动为你完成多边形的绘制。
由于多边形可以有任意数量的顶点,你可以创建越来越复杂的形状,像这样一个有八个顶点的形状,通过以下语句绘制(图 3-6):
polygon([
[ 0, 0], [20, 0],
[20, 5], [ 5, 5],
[ 5, 10], [20, 10],
[20, 15], [ 0, 15]
]);

图 3-6:一个具有八个顶点的更复杂的多边形
使用文本绘制单词
在设计中使用二维形状的另一种方法是创建符号化的图案,例如文字。使用文本元素可以为你的设计增添个性化。你也可以使用表情符号字体来访问预先绘制的符号,或者仅仅在设计上印上版本号或序列号。
使用text命令在 OpenSCAD 中绘制文本形状。OpenSCAD 中的文本(和其他编程语言一样)被视为字符串。由于字符串的长度可以是任意的,因此使用双引号(" ")来表示文本串的开始和结束。文本串可以包含字母、标点符号、数字,且如果所使用的字体支持 Unicode,还可以包含表情符号字符。
这条语句创建了字符串"Hello, OpenSCAD"(图 3-7):
text("Hello, OpenSCAD", size=10);

图 3-7:创建一个二维text形状
在text命令后面跟上括号,括号中包含一个字符串。字符串应该以双引号(" ")开始和结束。括号还可以包含一个可选的size参数,设置文本大小,这里设置为 10。注意在图 3-7 中,字符串中最高的字母达到了 y 轴上的第一个刻度(代表 10 单位)。
对于文本形状,size参数是可选的。如果省略size参数,默认的文本大小为 10。绘制文本形状的另一个可选参数是font。你还可以使用可选的font参数来绘制计算机上安装的任何字体的文本。以下语句绘制了一个 Courier 字体的文本串(图 3-8):
text("Hello, OpenSCAD", font="Courier");

图 3-8:将text形状的字体更改为 Courier
支持 Unicode 字符的字体通常包含表情符号。你可以绘制该字体支持的任何字符,包括表情符号形状(图 3-9):
text("♕", font="Arial Unicode MS");

图 3-9:使用text绘制王冠表情符号
也可以使用text命令绘制数字值。如果你想创建一个带有数字值的形状(图 3-10),请务必使用str函数将该值转换为字符串:
text(str(123), size=20);

图 3-10:绘制带有数字的text形状
不要将数字放在引号之间,而是对数字值应用str函数,将其转换为字符串。当数字值存储在变量中时,这尤其有用,正如我们在第四章中看到的那样。
对 2D 形状应用变换和布尔操作
你可以将第一章和第二章中学到的相同变换和布尔操作应用到 2D 形状上——这与应用于 3D 形状时几乎没有区别。唯一的不同是,translate、mirror和resize操作不再需要 3D 向量,而是需要包含 x 和 y 坐标的 2D 向量,而rotate操作只需要一个旋转角度(针对 z 轴)。
例如,以下设计使用translate、difference和rotate来绘制一个倾斜的矩形,并从中切出三个圆形(图 3-11):
rotate(30) {
difference() {
square([120, 40]);
translate([20, 20]) circle(15);
translate([60, 20]) circle(15);
translate([100, 20]) circle(15);
}
}

图 3-11:对 2D 形状应用变换和布尔操作
就像在 3D 形状中一样,对 2D 形状应用变换和布尔操作的顺序会影响最终形状的排列和位置。考虑从正方形中减去圆形与从圆形中减去正方形之间的区别。以下difference操作从正方形中减去圆形(图 3-12):
difference() {
square([5, 5]);
circle(5, $fn=50);
}

图 3-12:从正方形中减去圆形
而这个difference操作将一个正方形从圆形中减去(图 3-13):
difference() {
circle(5, $fn=50);
square([5, 5]);
}

图 3-13:从圆形中减去正方形
使用linear_extrude垂直拉伸形状
你不能直接 3D 打印 2D 形状,但可以将它们作为构建 3D 形状的基本构件(这些 3D 形状随后可以被 3D 打印成物理物体)。本节描述了 OpenSCAD 用于从 2D 形状创建 3D 形状的两个强大操作。
linear_extrude操作将一个平面形状沿 z 轴“提升”,同时构建与形状初始边界相对应的墙壁。以下语句将字母* A *挤压成一个具有 5 个单位高度的 3D 形状(图 3-14):
linear_extrude(5) text("A");
linear_extrude 操作接受一个参数,即你要创建的 3D 形状的高度,后跟你希望拉伸成 3D 的 2D 形状。和你已经知道的变换操作一样,整个语句以分号结束。
你也可以为 linear_extrude 操作提供可选参数 twist、slices 和 scale 来构建更复杂的 3D 形状。twist 参数指定在拉伸过程中扭转 2D 形状的角度。slices 参数控制扭转的平滑度——具体来说,控制完成扭转时使用多少个段落。由于拉伸会将形状向上扩展,因此每个段落都会变成一个水平的“切片”,这就是为什么该参数命名为 slices。如果你没有指定,OpenSCAD 会选择一个相对粗糙的值。scale 参数则在拉伸过程中改变 2D 形状的大小。

图 3-14:将 2D 形状线性拉伸成 3D 形状
使用所有这些参数,将矩形转换为在 图 3-15 中绘制的 3D 形状:
linear_extrude(100, twist=30, slices=25, scale=1/3) {
square(100, center=true);
}

图 3-15:将 2D 形状通过 25 个水平切片扭曲、缩放并扩展为 3D 形状
twist、slices 和 scale 参数是可选的。尽管这个例子展示了同时使用这三个参数,但你可以选择任意组合,例如只使用 scale 或只使用 twist。
沿圆形路径拉伸形状,使用 rotate_extrude
与其沿直线路径拉伸 2D 形状,不如使用 rotate_extrude 操作将 2D 形状沿圆形路径移动,从而创建一个像环形体(图 3-16)的形状:
rotate_extrude() {
translate([100, 0]) circle(40);
}

图 3-16:将 2D 圆形通过 rotate_extrude 操作转化为 3D 环形体
rotate_extrude 操作是一个两步过程,首先围绕 x 轴旋转 2D 形状 90 度,然后将 2D 形状沿 z 轴做圆形运动。如果你将结果中圆环的一部分切下来,这部分的形状会和原始的 2D 形状相似。
使用 rotate_extrude 时,要注意确保形状不会与自身相交。在绘制 图 3-16 的代码中,你可以通过先将形状从 z 轴上移开,确保 2D 形状的任何部分都不接触 z 轴,从而避免这种情况。
rotate_extrude 操作还接受一个可选的 angle 参数,允许你指定旋转角度。图 3-17 展示了一个在 z 轴上沿 135 度旋转拉伸的圆形。
rotate_extrude(angle=135) {
translate([100, 0]) circle(40);
}

图 3-17:带有 135 度角度参数的 rotate_extrude
使用 offset 进行形状的放大和缩小
假设你想制作一个别致的十字形饼干切割器。你现在知道如何通过联合两个矩形来创建十字形,并且知道如何使用linear_extrude将其拉伸为三维物体。但要指定墙厚,你需要使用offset操作,它允许你通过特定的值来扩展或缩小一个形状。使用offset来通过缩小一个十字形并从较大的十字形中减去它,来空心化你的饼干切割器。
在以下设计中,给offset传递负值来缩小你的二维十字形(图 3-18):
offset(-2) {
union() {
square([100, 30], center=true);
square([30, 100], center=true);
}
}
将要偏移的二维图形的代码放在大括号中,紧随offset操作。在括号内,指定偏移量(以毫米为单位)。正值将扩大形状,负值将缩小形状。

图 3-18:通过传递负值给offset来缩小物体
现在你可以重用该代码来构建十字形饼干切割器的墙面(图 3-19):
linear_extrude(30) {
1 difference() {
2 union() {
square([100, 30], center=true);
square([30, 100], center=true);
}
3 offset(-2) {
square([100, 30], center=true);
square([30, 100], center=true);
}
}
}

图 3-19:十字形饼干切割器
定义两个正方形,通过union操作创建外部十字形。接着,再定义两个正方形创建内部十字形,使用offset将内部十字形缩小,然后从外部十字形中减去它。这样,你就得到了一个空心的十字形。
导入二维图形文件
就像处理三维图形一样,你也可以从其他二维设计程序创建的文件中导入二维图形。OpenSCAD 支持导入.dxf和.svg这两种二维文件格式。这些格式通常与流行的二维矢量图形设计工具一起使用,比如 Adobe Illustrator 和 Inkscape(一个开源的 Adobe Illustrator 替代品)。OpenSCAD 只支持导入封闭的多边形形状,不能包含任何“开口”部分。此外,确保将.dxf文件中的所有线段转换为直线。
import命令的语法对于导入二维和三维图形是相同的。你只需要将文件名用引号括起来传递给import,并确保文件保存在与项目相同的文件夹/目录下。例如,使用以下语句导入图 3-20 中的图形:
import("drawing.dxf");

图 3-20:导入的.dxf矢量图形
尽管导入的文件看起来是圆形的,但它实际上由许多短线段组成,类似于你在本章早些时候学会创建的多边形。这个二维笑脸图形是使用 Inkscape 绘制的。该过程中的一个重要步骤是将形状中的所有线段转换为非常小的直线。
一旦导入了二维图形,它就像内建的图形一样,你可以对其进行变换,并与其他图形结合。以下语句首先导入图 3-20 中展示的笑脸形状,然后将其拉伸成图 3-21 中展示的形状:
linear_extrude(height=500, scale=3) import("drawing.dxf");

图 3-21:一个挤出并缩放后的 .dxf 向量图形
现在你拥有一个可以进行 3D 打印的笑脸形状,能够作为一个印章使用。
总结
在这一章中,你学习了如何基于 2D 形状设计并创建 3D 形状。现在你应该能够创建、组合和变换简单的 2D 形状,比如圆形、矩形、多边形和文本。你可以通过 offset 操作创建 2D 形状的内外轮廓,导入向量图形,并将 2D 形状转换为 3D 形状。
到现在为止,你应该能够想象出使用 OpenSCAD 的 2D 和 3D 形状可以创建的各种设计。有时候,先考虑复杂 3D 设计的 2D 投影,然后将其延伸为 3D 形状,会更容易构建。
以下是在处理 2D 形状时需要记住的一些重要事项:
-
渲染 2D 设计会显示该形状的实际 2D 视图,而设计的预览窗口则会在 z 轴上增加少量的高度。
-
3D 形状变换向量需要三个参数:[x, y, z];而大多数 2D 形状变换向量只需要两个参数:[x, y]。
-
2D 旋转仅需要一个参数:表示 xy 平面内旋转角度的数值。
-
顶视图通常能提供设计 2D 形状时最佳的视角。
-
挤出 2D 形状和文本是将它们与 3D 形状结合的必要步骤。
-
文本字符串以双引号开始和结束。
-
你可以使用
text形状通过将数值转换为字符串(使用str函数)来绘制数字值。 -
只有支持 Unicode 的字体才能用于绘制表情符号,但想想看,挤出表情符号形状会有多有趣!
-
使用
rotate_extrude对 2D 形状进行变换时,2D 形状的任何部分都不能跨越 z 轴。 -
将 2D 形状看作是通过
rotate_extrude操作生成的 3D 形状的“截面”。
第四章:使用循环和变量

从本章开始,你将学习如何使用 OpenSCAD 更加智能地工作,而不是更辛苦。首先,你将学习使用一个非常有用的编程工具——循环。循环让你用几行代码绘制许多相似的形状。
这在你的设计中包含重复特征时尤其有用。例如,如果你正在创建帝国大厦的模型,为大厦的每个窗户单独编写一条语句将花费大量时间。使用循环,你可以沿着固定的模式重复一个窗户,这样 OpenSCAD 就能处理复制粘贴同一个窗户多次的繁琐工作。你还将学习如何使用变量来跟踪与你的设计相关的重要数据。因为这些新的 OpenSCAD 工具将允许你创建更复杂的设计,所以你还将学习如何使用注释来为自己和其他协作者留下设计备注。
留下带注释的备注
在本章中,设计比前几章稍微复杂一些,所以我们将在代码示例中使用注释来解释设计中的重要细节。注释为你提供了一种为自己和其他可能阅读代码的人留下备注的方式。OpenSCAD 会忽略注释语句,因为它们仅作为给人类阅读的备注,而不是让 OpenSCAD 绘制特定形状的指令。
使用//编写单行注释
单行注释以//开始,直到行末为止。它们对于留下简短的备注非常有用,以便你日后能记住当时在创建 OpenSCAD 设计时的思考过程。
使用/* */编写多行注释
多行注释以/*开始,以*/结束。OpenSCAD 会忽略多行注释中的所有内容。多行注释在你想集中精力处理特定元素时非常有用,因为它可以临时忽略设计中的某些部分。多行注释使得一次性忽略多个语句变得容易。
以下代码展示了单行注释和多行注释,结果是只绘制了一个形状(一个长方体,图 4-1),因为其他的 OpenSCAD 语句被注释掉并被忽略:
cube([5, 10, 20]);
//sphere(5);
/*
cylinder(h=5, r1=10, r2=10);
cube([50, 50, 50]);
*/

图 4-1:在一组注释中的一个单一立方体
使用for循环重复代码
本章的主要重点是让 OpenSCAD 处理繁琐且容易出错的“复制粘贴”方式,避免在绘制一系列相似形状时一遍又一遍地编写类似的语句。例如,如果你想在一条直线上绘制 10 个相同的圆柱体,你可以写 10 条语句——每条语句处理一个圆柱体——只需要改变translate操作中的向量参数,以避免重叠,正如以下设计所示(图 4-2):
translate([10, 30, 0]) cylinder(h=4, r1=4, r2=4);
translate([20, 30, 0]) cylinder(h=4, r1=4, r2=4);
translate([30, 30, 0]) cylinder(h=4, r1=4, r2=4);
translate([40, 30, 0]) cylinder(h=4, r1=4, r2=4);
translate([50, 30, 0]) cylinder(h=4, r1=4, r2=4);
translate([60, 30, 0]) cylinder(h=4, r1=4, r2=4);
translate([70, 30, 0]) cylinder(h=4, r1=4, r2=4);
translate([80, 30, 0]) cylinder(h=4, r1=4, r2=4);
translate([90, 30, 0]) cylinder(h=4, r1=4, r2=4);
translate([100, 30, 0]) cylinder(h=4, r1=4, r2=4);

图 4-2:用 10 个单独语句或用单个for循环绘制的一排圆柱
请注意,从一个圆柱到下一个圆柱唯一的变化是沿 x 轴增加的位置:第一个圆柱的 x 位置是 10,第二个圆柱的 x 位置是 20,依此类推,直到最后一个圆柱的 x 位置为 100\。
与使用 10 个单独语句不同,你可以使用单个for循环来生成这一系列圆柱。你只需要编写一个循环,在 x 轴上绘制第一个圆柱 10 个单位,然后每次绘制一个新圆柱时,将 x 位置增加 10 个单位,直到绘制最后一个圆柱,位置为 100 单位。
以下伪代码展示了for循环的语法:
for (`variable` = [`start`: `increment`: `end`]) {
// one or more statements to be repeated
}
for关键字表示你希望重复执行 OpenSCAD 语句。然后你创建一个变量,用于在每次重复后跟踪变化的值。变量具有start值、increment值和end值。类似于将多个形状组合在一起应用单一变换,使用大括号({ })来包含所有你想重复的语句。
以下示例使用单个for循环绘制 10 个圆柱,而不是使用 10 个单独语句:
for (1x_position = [10:10:100]) {
translate([x_position, 30, 0]) cylinder(h=4, r1=4, r2=4);
}
一个名为x_position的变量用于跟踪每个圆柱体的位置。该变量的初始值为 10;每次for循环重复时,x_position的值增加 10,确保下一个圆柱体沿 x 轴绘制时远离 10 个单位。一旦x_position等于 100,最后一个圆柱体将被绘制,循环停止。最终生成的图形与使用 10 个单独语句绘制的效果相同,如图 4-2 所示。
你可以使用循环沿多种模式重复形状。图 4-3 展示了一个围绕 z 轴旋转的圆锥重复模式,下面是相应的for循环:
for (angle=[0:45:315]) {
1rotate([0, 0, angle]) 2translate([10, 0, 0]) 3cylinder(h=5, r1=2, r2=0);
}

图 4-3:围绕 z 轴旋转的十个圆锥,使用for循环生成
在大括号内,循环创建一个圆锥 3,沿 x 轴 2 平移 10 个单位,然后将其旋转angle度 1。当angle变量的值为 0 时,第一个圆锥被绘制,此时它没有被旋转。每次循环重复时,angle变量的值增加 45,旋转每个圆锥。angle变量的最后一个值为 315,因此最后一个由循环绘制的圆锥围绕 z 轴旋转 315 度。
使用 echo 调试 for 循环
有时,检查变量在 for 循环重复过程中的变化值是有用的。例如,如果你想检查你的心算是否正确,看到 for 循环生成的精确值会更容易。使用 echo 函数将变量的每个连续值打印到控制台窗口,并检查控制台窗口(图 4-4)来获取关于 OpenSCAD 代码执行的反馈:
for (x_position = [10:10:100]) {
translate([x_position, 30, 0]) cylinder(h=4, r1=4, r2=4);
echo("x:", x_position); //a good way to check your mental math
}

图 4-4:通过 echo 生成的控制台输出
echo 函数对于调试程序非常有用。例如,你可以用它来可视化控制 for 循环重复次数的变量的所有值。echo 函数为你提供了一种有效的方式来收集关于 for 循环的反馈,因为它会打印出 for 循环生成的每个值。在控制台语句中添加字符串标签(如 "x:")可以帮助整理控制台窗口的输出。在 echo 函数中的标签和变量应该用逗号(,)分隔。
使用变量和算术运算
变量与 for 循环一起使用,用来跟踪循环所创建的模式。你可以直接使用生成的值,或者对其进行算术运算,以产生更复杂的重复模式。
在本节中,你将学习变量命名的最佳实践、对变量执行的数学运算,以及变量在循环中的应用。
命名变量
前面 for 循环示例中的 x_position 变量和 图 4-3 中的 angle 变量并不是 OpenSCAD 内建的。这些名称是为了描述这些值在设计中的用途而选择的。x_position 变量描述的是圆柱体的 x 位置,而 angle 描述的是圆锥体的旋转角度。
OpenSCAD 允许你根据自己的需求命名变量,只要不包含空格或使用字母、下划线、数字以外的符号。务必选择一个有助于你记住变量用途的名称。这可以帮助你更轻松地跟踪设计中的多个变量,对于调试错误或共享设计时尤为重要。
对变量应用数学运算
要开始探索 OpenSCAD 如何对变量应用数学运算,假设你将值 10 和 3 赋给以下变量:
value1 = 10;
value2 = 3;
要执行像求和、差、积、商或余数这样的数学运算,OpenSCAD 允许你使用标准符号。
OpenSCAD 同样遵循你可能在数学课上熟悉的常规运算顺序。将每个算术运算的结果赋值给一个变量,能帮助你将计算语句与输出语句分开:
sum = value1 + value2;
difference = value1 - value2;
product = value1 * value2;
quotient = value1 / value2;
remainder = value1 % value2;
现在,使用echo函数显示每个数学运算的结果(图 4-5)。每个echo函数使用标签来帮助识别控制台窗口中每个数字对应的内容。
echo("Addition:", sum);
echo("Subtraction:", difference);
echo("Multiplication:", product);
echo("Division:", quotient);
echo("Modulo:", remainder);

图 4-5:五种算术运算符的控制台输出
在for循环中使用数学运算和变量
你可以在for循环中使用算术运算,使一个变量表示两个不同的模式。以下设计通过同一个for循环生成了 13 个球体(图 4-6):
for (faces=[3:11:15]) {
2 $fn = faces;
x_position = faces*10;
translate([3x_position, 0, 0]) sphere(r=5);
4 echo("faces:", faces, "x-position:", x_position);
}

图 4-6:一系列逐渐变得更加平滑的球体
请注意,由for循环创建的faces变量同时指定了渲染球体所用的面数 2 和球体沿 x 轴的位置 3。在每次重复for循环时,faces的值增加 1,而x_position的值通过将faces的新值乘以 10 来更新。echo函数 4 显示了faces和x_position的变化值。图 4-7 展示了控制台输出。

图 4-7:一系列逐渐变得更加平滑的球体的控制台输出
使用算术运算创造独特的图案
除了利用算术运算发挥for循环的作用,逐步改变形状的特征外,你还可以使用算术运算来创造有趣的图案。以下代码通过使用二次模式来增加每个圆柱体的高度,从而生成一系列高度逐渐增加的圆柱体(图 4-8):
for (1x=2[1:1:10]) {
height = 3x*x;
x_position = 45*x;
translate([x_position, 0, 0]) cylinder(h=height, r1=2, r2=2);
}
上述设计使用了一个for循环将一个变量x从 1 增加到 10。x变量在每次循环时增加 1,因此该循环会执行 10 次。该变量同时控制一系列圆柱体的 x 轴位置和高度。通过创造性地使用算术运算,每次循环时你都会将圆柱体的 x 位置增加 5。圆柱体的高度以不同的速率增长,每次循环时通过平方x的值来增加,这被称为二次增长。

图 4-8:一系列按二次模式增加高度的圆柱体
使用嵌套循环绘制二维和三维网格
OpenSCAD 甚至允许你重复一个循环,你可以在一个for循环内放入另一个for循环。你可以使用一个for循环来创建一排形状,而在另一个for循环内使用for循环,可以重复这排形状,从而创建一个形状网格,只需几行代码。这被称为嵌套循环。以下设计使用了嵌套的for循环来绘制一个圆柱体网格(图 4-9):
1 for (y_pos = [10:10:50]) {
2 for (x_pos = [10:10:100]) {
translate([x_pos, y_pos, 0]) cylinder(h=4, r1=4, r2=4);
3 echo("x:", x_pos, "y:", y_pos);
} // x_pos loop
} // y_pos loop
前面的代码使用一个循环绘制了一排 10 个圆柱体 2。这个for循环由第一个for循环 1 重复,因此圆柱体的排布会重复。两个变量——即x_pos和y_pos变量——协同工作,改变重复圆柱体的 x 和 y 位置。内层循环重复 10 次,而外层循环重复 5 次。这样总共会生成 50 个圆柱体。echo函数用于在控制台窗口 3 中跟踪这两个变量的值变化。注意,注释用于指示哪些括号属于哪个循环。虽然注释括号并非必要,但当你有许多相邻的花括号时,这样做会很有帮助。

图 4-9:使用嵌套for循环绘制的圆柱体网格
现在,你知道了如何用四行代码生成 50 个圆柱体,这无疑比写一长串 50 个语句来单独生成每个圆柱体要高效得多。这正是绘制摩天大楼中众多窗户的完美技巧。
使用嵌套循环生成摩天大楼的窗户
列表 4-1 使用嵌套for循环绘制了一个有 60 个窗户的建筑(图 4-10):
num_rows = 10;
num_cols = 6;
building_width = num_cols*5;
building_height = num_rows*6;
1 difference() {
2 cube([building_width, 10, building_height]);
3 for (z = [1:1:num_rows]) {
for (x = [0:1:num_cols-1]) {
4 x_pos = x*5+1;
z_pos = z*5;
translate([x_pos, -1, z_pos]) cube([3, 3, 4]);
} // x loop
} // z loop
} // difference
列表 4-1:使用嵌套for循环绘制一个有 60 个窗户的摩天大楼

图 4-10:摩天大楼上的窗户网格
列表 4-1 使用变量(命名为num_rows和num_cols)来控制不仅仅是窗户的数量,还包括建筑物的宽度和高度。首先,它绘制了一个大的长方体来表示建筑物 2。接着,它使用嵌套for循环绘制了一个 60 个长方体的网格 3。最后,difference操作从较大的建筑物中减去这些长方体,形成凹陷的窗户 1。两个变量(x_pos和z_pos)用于在绘制长方体 4 之前计算每个窗户的具体 x 位置和 z 位置。
我们在列表 4-1 中的代码组织使得修改摩天大楼的特性变得容易。num_rows和num_cols变量不仅控制了两个循环的重复次数,还设置了建筑物的宽度和高度,因为building_width和building_height变量的值依赖于num_rows和num_cols的值。对num_rows或num_cols做出任何更改都会完全改变摩天大楼的外观。你将在下一章学到更多关于这种代码组织的优势。
三重嵌套以创建 3D 形状网格
你还可以通过添加更多的嵌套层来绘制 3D 形状网格——也就是说,将一个循环放在另一个循环里,再放进另一个循环——尽管这可能需要一些时间来渲染,因为它会生成大量的形状(图 4-11):
for (r = [0:15:255]) {
for (g = [0:15:255]) {
for (b = [0:15:255]) {
translate([r, g, b]) color([r/255, g/255, b/255]) cube(5);
} // b loop
} // g loop
} // r loop

图 4-11:表示 RGB 颜色空间的嵌套for循环
这个 三重嵌套 实际上使用第三个循环来重复形状的网格。前面的设计使用了三个嵌套循环来绘制一个表示 RGB(红色、绿色、蓝色)色彩空间的立方体。color 转换接受一个 3D 向量,表示应该在形状的颜色中展示的红色、绿色和蓝色光的百分比。由于 RGB 使用 255 作为最大值,除以 255 得到一个 0 到 1 之间的小数。color 转换对于调试和组织设计很有用,但对于 3D 打印来说作用不大,因为 3D 打印的颜色完全取决于所使用的材料类型。因此,color 转换只在预览模式下有效,在渲染模式下不会显示。
概述
本章介绍了循环的概念,循环允许你在不重复编写相同代码的情况下重复执行语句。通过循环,你可以让计算机代替你重复编写语句的所有工作。变量是 OpenSCAD 中循环的重要组成部分,尽管它们并不局限于循环。变量还可以帮助你跟踪重要的值。通过算术运算符,变量可以作为其他变量的起始点,这在你想改变设计时非常有用。
以下是使用循环的一些重要提示:
-
如果你发现自己在复制、粘贴并稍作修改以重复某个语句,考虑使用循环来生成这些重复操作。
-
使用算术运算基于循环创建的模式进行复杂的重复操作。
-
给变量起一个能描述其用途的名字。
-
将所有变量组织在程序顶部可以让你更容易地修改设计。
-
使用
echo函数输出变量的值,随着循环的重复,这可以帮助你跟踪通过复杂算术得到的变量。 -
给所有
echo函数的输出加上标签,这样在有嵌套循环时,你可以输出多个变量。 -
如果你想在
text形状中使用由for循环生成的变量值,记得使用str将数字转换为字符串(如第三章所提到的)。 -
color转换对于在预览模式下调试非常有用,但它不会应用于渲染模式或 3D 打印。 -
注释是程序员用来帮助解释编程选择的注解。
-
OpenSCAD 会忽略注释,但人类使用注释来帮助理解代码语句的目的。
第五章:模块

在本章中,你将学习如何将复杂的设计转化为更易管理的组件,这些组件被称为模块。模块是代码的独立部分,它们组织了一系列独立的语句,特别有两个好处。如果你的代码很长且复杂,使用模块可以将代码分解成更小的子部分,有助于提高代码的可读性。如果你的设计中有重复或相似的形状,你可以使用一个模块来定义该形状,从而减少需要编写的代码量,便于创建复杂的设计。
本章还介绍了如何使用变量和参数来自定义你的模块。最后,我们将解释如何将相似的模块分组到一个单独的文件中(通常称为库),以便更容易地组织设计、共享设计并使用别人创建的设计。
简化代码与模块
为了理解使用模块如何简化你的代码,让我们再看看你在第三章中构建的十字形饼干切割器的代码(图 5-1)。

图 5-1:十字形饼干切割器
我们已将代码复制到列表 5-1 中。你能看到任何重复的代码吗?
linear_extrude(30) {
difference() {
union() {
square([100, 30], center=true);
square([30, 100], center=true);
}
offset(-2) {
square([100, 30], center=true);
square([30, 100], center=true);
}
}
}
列表 5-1:原始的十字形饼干切割器程序
这个饼干切割器是通过取两个十字形的差来制作的,因此square命令用于创建十字形的代码被重复了两次。重复的代码几乎总是会导致问题,因为你对形状的尺寸所做的任何更改都必须重复进行(或者根据代码被重复的次数)。如果你忘记修改每个实例,你将不得不花时间修复它,或者更糟糕的是,最终在设计中留下长期的错误。
为了改进这个设计,你可以使用一个模块来创建一个十字形,然后利用这个模块来创建两个十字形。以下伪代码展示了模块定义的语法:
module `ModuleName`() {
// code used to define the new shape
}
使用module关键字开始定义一个新模块。然后给模块起个名字,描述你正在创建的新形状。模块名的限制与变量名相同,这意味着你只能使用小写和大写字母、下划线或数字 0 到 9。一个好的名字应该能帮助读者理解模块的功能,而不需要他们阅读定义模块的实际代码。在ModuleName后添加一对空的括号,接着是用大括号括起来的代码。你在大括号内写的代码与任何其他 OpenSCAD 代码没有区别。
模块定义将作为设计的一个独立部分存在。因此,定义一个模块并不会实际绘制出新的形状。它仅仅是一个描述如何创建形状的配方。要看到这个形状,你必须通过将模块名插入到你的设计中来创建它,就像创建其他任何形状一样。下面是使用模块的语法:
*ModuleName*();
模块是程序员定义的形状的示例。事实上,迄今为止你使用过的所有 OpenSCAD 命令,包括sphere、cylinder和linear_extrude,实际上都是内置于语言中的模块。当形状在模块内结合时,会隐式发生union操作,因此你可以使用你到目前为止见过的任何操作来变换和组合模块生成的形状。
通过创建一个cross模块来为你的饼干切割器编写一些新代码,如示例 5-2 所示。
module cross()1 {
square([100, 30], center=true);
square([30, 100], center=true);2
}
linear_extrude(30) {
difference() {
3 cross();
4 offset(-2) cross();
}
}
示例 5-2:使用模块改进的新十字形饼干切割器程序
使用module关键字开始定义新形状。给它起名为cross 1,以描述你正在创建的形状。在名称后面的花括号中,输入定义十字形状的代码 2。最后,使用模块名称后跟一对括号 3 4,告诉 OpenSCAD 绘制该十字形。注意,你使用了两次cross模块,因此你可以用difference操作将一个十字形从另一个中减去。
将设计拆分成多个文件
有时在创建新设计时,你可能想要重用先前项目中的一个组件。一个很好的组织方法是将该组件做成一个模块。将这个模块定义放到一个单独的文件中,可以让你在两个设计中轻松使用它。单独保存模块有助于你在多个项目中查找并重用新形状,也可以轻松地与他人分享它们。而且,如果你对一个由多个设计使用的文件中定义的模块进行了改进,那么这些改进将在你下次打开每个设计时自动应用。将模块定义组织成单独的文件通常被称为创建一个库,特别是当一个新文件中定义了多个相关模块时。
要了解如何将模块保存在单独的文件中,我们将十字形饼干切割器设计拆分为两个文件。我们将使用一个文件来定义十字形状,然后在第二个文件中使用该模块来创建饼干切割器。首先,创建两个空的 OpenSCAD 文件:cross-module.scad和cookie-cutter.scad。确保将这两个文件保存在同一个文件夹中,以便 OpenSCAD 可以找到这两个文件。此外,注意这些文件名是为了清楚地表明每个文件的用途而选择的。仔细选择文件名将有助于你在将来组织你的项目,特别是当你构建越来越多的 OpenSCAD 项目时。
在cross-module.scad中,复制示例 5-2 中的模块定义,包括花括号,然后将其粘贴到你刚刚创建的文件中。确保在粘贴代码后保存cross-module.scad,这样 OpenSCAD 就能在连接文件时使用最新版本。新的cross-module.scad文件应仅包含以下代码:
module cross() {
square([100, 30], center=true);
square([30, 100], center=true);
}
现在在cookie-cutter.scad中,移除模块定义并在文件顶部添加以下一行:
**use <cross-module.scad>**
linear_extrude(30) {
difference() {
cross();
offset(-2) cross();
}
}
不需要在cookie-cutter.scad中键入模块定义,第一行代码告诉 OpenSCAD 使用cross-module.scad中的代码。这提供了交叉形状的定义。
use关键字告诉 OpenSCAD 从另一个文件加载模块。use关键字的语法如下:
use `<path/to/filename.scad>`
在use关键字后,添加尖括号(< >),并在尖括号内指定你想使用的.scad文件的名称。如果你想使用的文件不在与主设计文件相同的文件夹中,指定该文件的绝对路径或相对路径。use语句允许你使用文件中的模块定义,但它不会立即绘制任何形状。
生成cookie-cutter.scad的预览现在会产生与图 5-1 中相同的形状。然而,生成cross-module.scad的预览不会产生任何形状。这是因为cross-module.scad目前只包含cross模块的定义。为了通过生成cross-module.scad的预览来查看交叉形状,你需要添加一个语句来绘制交叉形状:
cross();
module cross() {
square([100, 30], center=true);
square([30, 100], center=true);
}
向你的模块添加参数
因为形状有不同的尺寸,你可能希望你的模块允许一些变动。你已经知道,内置的 OpenSCAD 模块,比如sphere,可以接受一个参数,如sphere(r=30);,其中参数指定了球体的半径。你也可以向自己的模块添加这样的参数。
以下伪代码展示了指定模块的完整语法,包括参数:
module `ModuleName`(`parameterName` = `defaultValue`, ...) {
// statements used to define the shape
}
不要将模块定义后的括号留空,而是添加一个parameterName,这是一个占位符,用于存放你在使用模块时提供的值。你还可以为每个参数提供一个defaultValue,这样,如果模块的使用者没有为某个参数指定值,模块将使用默认值。提供默认值使得人们在使用模块时无需指定所有参数,这在实验模块时非常有用,或者当默认值是常见选择时,它可以隐藏分散注意力的细节。要创建多个参数,指定多个参数名,用逗号分隔,并确保每个参数有不同的名称。
你可能已经注意到,参数看起来很像变量。事实上,在模块内部,参数表现得就像变量一样。最佳实践是给参数命名,以描述其用途。与变量和模块名称一样,参数名称只能包含字母、下划线或数字。
示例 5-3 展示了如何向cross模块添加参数:
module cross(width=30, length=100) {
square([length, width], center=true);
square([width, length], center=true);
}
示例 5-3:使用参数定义cross模块
在括号内,添加width和length参数,这些参数定义了交叉形状每个臂的宽度和长度。
要使用cross模块创建一个十字形,每次使用该模块时提供每个参数的具体值,如清单 5-4 所示。
use <cross-module.scad>
linear_extrude(30) {
difference() {
cross(20, 100);
offset(-2) cross(20, 100);
}
}
清单 5-4:指定 cross 模块的值
数字的顺序表示应解释为十字的宽度还是长度。由于width参数在模块定义中排在前面,括号中的第一个数字被分配给width参数,第二个数字分配给length参数。
OpenSCAD 还允许你在使用模块时显式命名参数,这在创建具有大量参数的形状时很有帮助(因为跟踪顺序会变得困难):
cross(width=20, length=100);
当你使用模块并命名参数时,参数的顺序并不重要。交换长度和宽度参数的顺序不会影响形状的外观:
cross(length=100, width=20);
现在模块真正变得动态;你可以用它来创建任何大小的饼干模具(图 5-2)。

图 5-2:各种饼干模具,每个模具都是使用不同参数创建的
构建一个 LEGO 积木
在本节中,我们将通过一个复杂的建模项目,使用参数、模块和for循环进行单一设计。你将设计一个 LEGO 积木形状,具有一个方向的两个圆头和另一个方向上任意数量的圆头。圆头是 LEGO 积木顶部的小凸起,能够与其他 LEGO 积木配合固定它们。图 5-3 显示了一个有两排、每排四个圆头的 LEGO 积木。

图 5-3:具有 2×4 网格圆头的 LEGO 积木
在编写像这样的复杂设计代码之前,先手绘几份你形状的草图,可以帮助你深入理解形状中的尺寸和模式(图 5-4)。

图 5-4:手绘的各种尺寸 LEGO 积木的尺寸探索图
LEGO 积木的尺寸可以在网上轻松找到。我们从维基百科获取了我们的尺寸:
-
一块积木的高度是 9.6 毫米。
-
圆头的高度是 1.7 毫米。
-
圆头的直径是 4.8 毫米。
每新增一个圆头,积木的宽度增加 8 毫米,不仅是为了容纳圆头的直径,还有圆头周围的空隙。积木的长度也取决于圆头的数量。在这个例子中,你将只生成有两排圆头的积木,这意味着该积木的固定长度为 16 毫米。
探索各种手绘的 LEGO 形状,可以更容易地识别定义 LEGO 积木模块所需的 OpenSCAD 语句。
清单 5-5 定义了 LEGO 积木模块。
module LEGObrick(studs_per_row=4) {
$fn=30;
width = studs_per_row * 8;
cube([width, 16, 9.6]);
for (x_position=[4 : 8 : width-4]) {
translate([x_position, 4, 1.7]) cylinder(h=9.6, d=4.8);
translate([x_position, 12, 1.7]) cylinder(h=9.6, d=4.8);
}
}
LEGObrick(4);
清单 5-5:使用模块绘制 LEGO 积木
首先创建一个名为LEGObrick的模块,并带有一个studs_per_row参数。这个参数表示 LEGO 积木顶部的圆点数,它决定了积木在 x 轴上的整体宽度。LEGO 积木有不同的尺寸,因此这个参数将有助于重用相同的模块来绘制多种尺寸的积木。我们选择将每行 4 个圆点作为默认值,但这是一个任意选择。
创建一个名为width的变量,用于跟踪积木的整体宽度,它是基于studs_per_row的。每增加一个圆点,积木的宽度增加 8 mm:
width = studs_per_row * 8;
LEGO 积木的其他尺寸保持固定,和每行的圆点数无关:
cube([width, 16, 9.6]);
使用for循环将每个重复的圆点绘制到正确的位置:
for (x_position=[41 : 82 : width-43]) {
translate([x_position, 4, 1.7]) cylinder(h=9.6, d=4.8);
translate([x_position, 12, 1.7]) cylinder(h=9.6, d=4.8);
}
在for循环中,变量x_position跟踪每个圆点的 x 位置。第一个圆点位于 x = 4 mm 1 处,每个额外的圆点距离前一个圆点 8 mm 2。类似地,每行中的最后一个圆点位于砖块整体宽度的 4 mm 位置 3。两行圆点在 x 轴上绘制时,使用了完全相同的值。由于我们将 y 轴限制为只有两个圆点,所以直接将两行显式定位在 y = 4 mm 和 y = 12 mm 处,比使用第二个循环更简单。
LEGObrick模块现在已经完成,这意味着你可以使用它来创建不同大小的 LEGO 积木,就像图 5-5 中展示的那样。

图 5-5:使用相同的LEGObrick模块创建的各种 LEGO 积木
这个模块仅仅是一个简化版的 LEGO 积木设计;然而,它并不会像真实的积木那样工作,因为当前的设计没有包含积木底部用于连接积木的内置机制。我们将这个挑战留给你。
共享与协作
如果你将模块保存在不同的文件中,你可以像本章前面所展示的那样,在多个设计中重用这些新的形状。保持模块独立还允许你与其他人共享公共设计组件,或者使用他人的组件,而不是自己从头开始构建所有内容。将设计拆分成多个模块可以让你更容易进行协作。
让我们来看一个可能的协作案例。假设你和一个朋友想要一起制作一个 LEGO 城堡的 3D 动画。为了节省时间,你们决定将设计拆分成两个任务,并且可以在两台不同的计算机上并行完成。你的朋友决定设计一个绘制 LEGO 积木形状的模块,而你将负责设计一个由 LEGO 积木形状构成的城堡。
你和你的朋友首先决定LEGO模块应是什么样的。你们商定了模块的名称(LEGObrick)、任何必要的参数及其默认值(studs_per_row),默认值为三颗凸点,以及每块砖的基本形状和尺寸(一个 3×2 砖的尺寸为 24 × 16 × 9.6 毫米)。然后,你的朋友开始在名为LEGObrick-module.scad的文件中构建LEGObrick模块的简化版,如图 5-6 所示:
LEGObrick();
module LEGObrick(studs_per_row=3) {
cube([24, 16, 9.6]);
}

图 5-6:LEGObrick模块的简化版
即使LEGObrick模块尚未完成(该简化版模块尚未包含凸起的圆点),你仍然可以将其作为构建块,在名为castle-wall.scad的文件中开始创建城墙设计,如图 5-7 所示。

图 5-7:使用基本版LEGObrick模块的 LEGO 城堡墙壁
同时,你的朋友继续改进LEGObrick模块,每次改进后,他们会将更新版本的LEGObrick-module.scad分享给你。由于 OpenSCAD 设计是纯文本文件(.scad 扩展名),你可以通过电子邮件附件共享文件、直接从电子邮件或其他文档中复制粘贴 OpenSCAD 代码,或者使用像 GitHub 这样的高级服务来公开设计。也有一些专门的 3D 设计分享网站,其中一个更受欢迎的网站,直接支持 OpenSCAD,是 Thingiverse(thingiverse.com/)。
每次你的朋友分享更新版本的LEGObrick-module.scad时,你都需要将旧版本的文件替换为新版本。每次你预览或渲染你的城堡代码时,位于castle-wall.scad中的城堡设计都会更新为使用最新的LEGObrick定义。随着时间推移,你的设计可能会看起来更像图 5-8 所示的样子。

图 5-8:使用更新版LEGObrick模块构建城堡
这种协作策略节省了时间,因为你和你的朋友可以同时在LEGObrick模块和城堡设计上工作。你不必等你的朋友完成某一部分才能开始自己的部分,同时你的朋友也可以看到他们的模块设计中小的变动如何影响整体城堡设计。
总结
在本章中,你学会了如何通过使用模块将设计组织成更小的逻辑组件,这样可以让你的 OpenSCAD 代码更易读,便于协作,并帮助你定制设计。
使用模块时,请记住以下关键概念:
-
模块定义包括三个部分:名称、参数列表和主体。
-
模块的主体包含一组 OpenSCAD 语句,定义了模块的独特形状。
-
要使用模块,在设计中通过模块的名称创建形状。如果模块没有显示出来,检查一下你是否在代码语句中真正使用了模块名称;可能你只是定义了模块。
-
在设计模块时,选择能够清晰描述其目的的模块名称和参数,这样使用你模块的人就无需阅读模块定义便能了解其功能。如果你以后忘记了模块的细节,这也会对你有所帮助。
-
参数对于指定模块的特性非常有用。确定哪些变量应该作为参数包含是设计模块的重要部分。
-
为参数指定默认值是一种使某些参数变为可选的有用方法。
-
将模块定义分离到其他文件中有助于你在其他 OpenSCAD 设计中使用该模块。你还可以将相关模块组织到一个库中。与模块和变量一样,选择能够充分描述其目的的文件名。
-
使用
use关键字将你的设计与模块连接起来不会立即将新形状添加到你的设计中。你必须在代码中明确使用该模块才能看到新形状。 -
在模块定义文件的顶部绘制由模块定义的形状是常见做法,这对于测试非常有帮助。
尝试在线搜索 OpenSCAD 模块的示例,看看更多用户定义的形状。通过检查和修改其他人的解决方案,你可以学到很多,尤其是在确定应该包含哪些参数时。
第六章:使用 if 语句创建动态设计

在本章中,你将学习如何使用if语句来创建根据不同条件做出反应的 OpenSCAD 设计。这个强大的编程工具赋予你的设计根据不同选项选择并仅执行某些代码行的能力。因此,你可以创建适应变化情况的动态设计。例如,你可以使用if语句快速重新配置设计,以便进行 3D 打印。
作为一个示例项目,在这里你将学习如何使用if语句根据刻度线的位置来改变尺子上刻度的长度,以表示英寸、半英寸和四分之一英寸的增量。你还将学习如何使用随机数来改变重复的形状,以创建更有机的设计特征变化。
使用 if 语句
if语句使用布尔表达式(一个计算结果为true或false的表达式)来比较两个值,然后根据该比较决定是否执行代码。如果if语句中的布尔表达式计算结果为true,则执行指定的代码语句。否则,这些语句将完全跳过。布尔表达式描述了一个条件,只有当满足该条件时,指定的语句才会被添加到设计中。
以下是if语句的语法:
if (`<boolean expression>`) {
// code that is executed only when the boolean expression is true
}
列表 6-1 是第四章中创建的摩天大楼设计的一个变体。这个新版本使用if语句来决定将窗户和门放置在摩天大楼的哪个位置(图 6-1)。
num_rows = 10;
num_col = 6;
building_width = num_col * 5;
building_height = num_rows * 6;
difference() {
cube([building_width, 10, building_height]);
for (1 z = [1:1:num_rows]) {
for (x = [0:1:num_col-1]) {
2 if (z == 1) {
3 translate([x*5+1, -1, -1]) cube([3, 3, 8]); // door
}
4 if (z > 1) {
5 translate([x*5+1, -1, z*5]) cube([3, 3, 4]); // window
}
}
}
}
列表 6-1:使用if语句根据楼层编号插入门和窗户
图 6-1 显示了一座摩天大楼,第一层有门,后续每一层都有窗户。在列表 6-1 中的两个for循环创建了这个设计中的窗户和门的行列。z变量 1 控制每一行的垂直位置。接下来,两个if语句使用这些z值来决定是否将窗户或门添加到设计中。如果z等于 1 2,则会在设计中添加一个大门 3。如果z大于 1 4,则会在设计中添加一个小窗户 5。

图 6-1:摩天大楼上的窗户网格,带有一排门
我们将在本章的其余部分中不断发展这一摩天大楼设计。然而,你可能会觉得没有更多细节的摩天大楼不算摩天大楼,尤其是建筑的其他面。我们完全同意这一点,并将添加更多细节的任务留给读者,作为对这个简单设计的挑战。
定义复杂条件
你可以使用 if 语句通过结合六个布尔运算符和两个逻辑运算符中的一个来评估多种类型的条件。此外,你还可以通过将 else 语句与 if 语句连接,指定一个默认场景(当指定的条件为假时执行)。最后,你可以通过使用 else if 语句将多个相关的条件连接在一起。
选择布尔运算符
OpenSCAD 使用六个布尔运算符来评估布尔表达式中变量的内容。每个运算符如果比较有效,则返回 true,如果比较无效,则返回 false:
-
<小于 -
>大于 -
<=小于或等于 -
>=大于或等于 -
==等于 -
!=不等于
许多布尔运算符的符号可能你在数学课上已经很熟悉。OpenSCAD(与大多数其他编程语言一样)稍微改变了符号,以便你可以轻松地在键盘上输入。例如,你可能习惯看到 ≤ 符号表示“小于或等于”。然而,编程语言通常使用 <= 来代替。同样,>= 代替了 ≥,!= 代替了 ≠。最后,一定不要混淆 == 和 =。由于单个等号已经有了用途(为变量赋值),布尔表达式使用双等号 (==) 来测试两个值是否“相等”。例如,Listing 6-1 通过使用等号 (==) 运算符测试两个值的相等性。
这一组布尔运算符提供了许多选择,可以评估变量并确定某个条件是 true 还是 false。现在你可以编写一个循环,根据循环执行的次数生成不同的形状。如你所见,你还可以指定仅在某个条件不成立时绘制一个形状。在 if 语句中使用布尔运算符使你能够用相对较少的语句创建动态设计。
使用逻辑运算符组合布尔表达式
此外,你可以使用两个逻辑运算符之一来组合多个布尔表达式:&&(表示 且)和 ||(表示 或)。
如果使用 && 运算符,所有条件必须为真,才能执行指定的语句。如果使用 || 运算符,多个条件中至少有一个为真即可。为了更好地理解 && 运算符的工作方式,请考虑以下情况:
if (x > 10 && y <= 20) {
translate([x, y, 0]) cube([3, 4, 3]);
}
这段代码仅当 x 大于 10 且 y 小于或等于 20 时绘制一个平移的立方体。
现在考虑这个使用 || 运算符的 if 语句:
if (x > 10 || y <= 20) {
translate([x, y, 0]) cube([3, 4, 3]);
}
当任意x大于 10或者y小于或等于 20 时,会绘制一个平移的立方体。连接的或运算符的布尔表达式中,只有一个为true时,形状就会被绘制。如果连接的两个布尔表达式都为true,立方体也会被绘制。
遵循扩展的运算顺序
你可以构造涉及许多算术、布尔和逻辑运算符的复杂布尔表达式。就像在数学课上学会在加法之前先进行乘法,即使加法在算术表达式中排在前面一样,OpenSCAD 也按照一个明确的运算顺序来评估表达式:
-
( ) -
^ -
*,/,% -
+,- -
<,>,<=,>= -
==,!= -
&& -
||
在运算顺序中,处于相同级别的运算符按照它们在表达式中从左到右的出现顺序执行。否则,位于该列表顶部的运算符具有更高的优先级,并在位于列表底部的运算符之前进行计算,即使这意味着从内到外计算表达式。
使用 if…else 语句做出双向选择
一个基本的if语句仅在布尔条件为true时执行一段代码。若布尔条件为false时执行备用代码,可以在if语句后附加一个else语句。if...else语句在代码中创建了一个双向分支,使你能够根据不同的真值条件执行不同的语句集合。
请考虑以下if...else语法:
if (`<boolean expression>`) {
// code that is executed only when the boolean expression is true
}
else {
// code that is executed only when the boolean expression is false
}
如果if语句中的布尔表达式为true,则执行第一组语句。如果if语句中的布尔表达式为false,则执行else部分中的语句。if语句的else部分通常称为默认条件,因为它描述了在if语句指定的条件为false时应发生的情况。else语句是if语句的可选扩展,适用于互斥分支场景,在这种场景中,不可能同时包含代码的两个部分。
你可以通过使用else语句轻松重构示例 6-1。图 6-1 中的摩天大楼有且仅有一行门。其余所有行都将有窗户。因为for循环有时需要绘制门,而其他时候需要绘制窗户,所以你可以像这样重写if语句:
num_rows = 10;
num_col = 6;
building_width = num_col * 5;
building_height = num_rows * 6;
difference() {
cube([building_width, 10, building_height]);
for (z = [1:1:num_rows]) {
for (x = [0:1:num_col-1]) {
if (z == 11) {
2 translate([x*5+1, -1, -1]) cube([3, 3, 8]); // door
}
else {
3 translate([x*5+1, -1, z*5]) cube([3, 3, 4]); // window
}
}
}
}
如果布尔表达式z == 1的值为true,OpenSCAD 会绘制一个门 2。如果布尔表达式为false,OpenSCAD 会绘制一个窗户 3。
使用扩展的 if 语句
一个扩展的if语句将一个条件附加到else语句上,创建一个有序的相关决策集合。OpenSCAD 会按顺序评估扩展if语句中的布尔表达式,直到其中一个表达式评估为true。你可以选择性地在扩展if语句的末尾包含一个else语句,以便在所有决策的评估结果为false时提供一个兜底的默认选项。
扩展if语句的语法如下所示:
if (`<boolean expression>`) {
// code that is executed only when the boolean expression is true
}
else if (`<boolean expression>`) {
// code that is executed only when the first boolean expression is false
// and the second boolean expression is true
}
else {
// optional default scenario
// code that is executed only when both boolean expressions are false
}
你可以根据需要添加任意数量的else if语句来描述任何数量的互斥设计可能性,特别是当你想确保恰好一个相关的结果发生时非常有用。扩展if语句中的每个布尔表达式会按顺序进行评估,直到找到一个评估为true的表达式。只有该布尔表达式对应的代码部分会被执行,而其他部分会被跳过。如果没有布尔表达式的评估结果为true,则执行可选的else部分(如果提供的话)中的代码。由于else部分描述的是默认的可能性,它必须放在扩展if语句的末尾。
Listing 6-2 使用扩展的if语句在尺子上绘制各种大小的刻度线(图 6-2)。此设计创建了一个基于英寸的尺子,刻度线间隔为三种重复的间距:1 英寸、半英寸和 1/4 英寸。1 英寸间隔的刻度线最长,其次是略短的半英寸刻度线,最后是更短的 1/4 英寸刻度线。OpenSCAD 没有单位,因此该设计使用基本的比例关系将尺子上的每英寸划分为四个相等大小的“间隙”。它的设计意图是在 3D 打印准备软件中将其调整到准确的宽度后再进行打印。
ruler(5);
module ruler(inches) {
1 gap_size = 1; // 1 unit per quarter inch
total_marks = 4 * inches; // inch, half inch, quarter inch
width = gap_size * total_marks;
length = 4 * gap_size;
height = 0.5 * gap_size;
mark_width = 0.25 * gap_size;
mark_height = 1.5 * height;
// main ruler body
difference() {
cube([width, length, height]);
translate([width-gap_size, length-gap_size, -0.5])
cylinder(h=height+1, r=0.15*length, $fn=20);
}
// tick marks
2 for(t = [1:1:total_marks-1]) {
mark_x = gap_size * t - 0.5 * mark_width;
3 if (t%4 == 0) { // inch marks and number labels
translate([gap_size * t, 0.65 * length, 0])
linear_extrude(mark_height)
text(str(t/4), size=gap_size, halign="center");
translate([mark_x, 0, 0])
cube([mark_width, 0.5 * length, mark_height]);
}
4 else if (t%2 == 0) { // half-inch marks
translate([mark_x, 0, 0])
cube([0.75 * mark_width, 0.25 * length, mark_height]);
}
5 else { // quarter-inch marks
translate([mark_x, 0, 0])
cube([0.5 * mark_width, 0.125 * length, mark_height]);
}
}
}
Listing 6-2:使用扩展的if语句来区分尺子上的刻度线大小

图 6-2:一个五英寸的尺子
首先,定义一组变量来帮助我们组织设计 1:gap_size表示每一个 OpenSCAD 单位将代表刻度线之间单个 1/4 英寸间隙的宽度,total_marks记录需要的总刻度数(根据ruler模块的inches参数)。我们每英寸需要四个刻度,因为我们将在英寸、半英寸和 1/4 英寸的位置添加刻度线。其他变量则将尺子的不同特征的比例关系与这两个初始选择相关联。以这种方式组织模块变量可以让你在未来快速更新设计。例如,你可能决定在下一个版本中制作一个更长的尺子。这个变化可以通过做一个简单的更改来轻松实现:计算length变量值的公式。
for 循环 2 会为每个需要的刻度线绘制相应的内容,除了第一个和最后一个刻度线,它们是需要推断的(因为它们是尺子的起始和结束位置)。for 循环中的 t 变量跟踪绘制的刻度线数量,而 mark_x 用于跟踪每个新刻度线在 x 轴上的位置。第一个布尔表达式 3 检查 t 变量是否能被 4 整除(记住,% 计算余数)。如果条件为 true,最长的刻度线将被添加到设计中,表示一英寸的间隔。如果 t 变量不能被 4 整除,第二个布尔表达式 4 会检查它是否能被 2 整除。如果可以,则会添加第二长的刻度线,表示半英寸标记。只有当 t 变量既不能被 4 整除,也不能被 2 整除时,else 语句才会添加最短的刻度线到设计中 5。
注意在这个扩展的 if 语句中决策的顺序。for 循环产生一个数字序列,每个数字都由扩展的 if 语句进行评估:1、2、3、4、5、6、7、8,依此类推。像 4、8 和 12 这样的数字既能被 4 整除,也能被 2 整除,那么应该执行哪个条件呢?扩展的 if 语句会按顺序评估每个决策,只执行第一个布尔表达式为 true 的 if 语句中的代码。即使一些数字能被 4 和 2 都整除,第二个决策 3 也只有在第一个表达式 2 为 false 时才会被评估。因此,对于 for 循环中的每个 t 值,只会绘制一个刻度线。这是一个互斥的场景:每个 t 值只应该绘制三种刻度线中的一种,且仅绘制一种。
使用嵌套 if 语句
将 if 语句放入另一个 if 语句中是一种保证只有当另一个布尔表达式为 true 时,才考虑当前布尔表达式的方式。从基本层面来看,嵌套的 if 语句可以替代 && 运算符:
if (x < 8 && y == 10) {
// code that is executed only when both boolean expressions are true
}
所以你可以用嵌套的 if 语句重写上面的代码:
if (x < 8) {
if (y == 10) {
// code that is executed only when both boolean expressions are true
}
}
对于需要满足某些设计条件的简单布尔表达式组合,使用 && 运算符可能是最简单的。但是,当你想测试多个布尔表达式的结果,这些表达式的值可能是 true 或 false 时,使用嵌套的 if 语句会更加方便:
if (x < 8) {
if (y == 10) {
// code that is executed only when both x < 8 and y == 10
}
else if (y < 10) {
// code that is executed only when both x < 8 and y < 10
}
else {
// code that is executed only when both x < 8 and y > 10
}
} else {
if (y == 10) {
// code that is executed only when both x >= 8 and y ==10
}
else {
// code that is executed only when both x >= 8 and y !=10
}
}
通常可以使用布尔运算符、逻辑运算符、扩展的 if 语句和嵌套的 if 语句组合来描述复杂的条件。通常,最好的选择是组合那些对于设计创建者来说最有意义的条件。
if 语句的有用应用
每当你希望 OpenSCAD 设计根据特定条件变化时,你应该使用 if 语句。以下情况是你在项目中可能需要使用 if 语句的示例。
设置设计模式和打印模式
考虑第四章中的汉诺塔项目。在设计堆叠圆盘系列时,直观地将圆盘堆叠在某个桩子上是方便的。然而,这种配置并不适合 3D 打印设计,因为圆盘相互堆叠在一起,而你不希望所有的圆盘都作为一个整体打印出来。
一个有用的技巧是创建设计的两个版本:一个用于可视化最终结果,另一个用于 3D 打印它。使用设计模式以便于可视化的方式构建设计,使用打印模式以更适合 3D 打印的方式重新组织相同的设计。
清单 6-3 结合了这两种设计配置;图 6-3 展示了打印模式。
$fn = 100;
mode = "print"; // or "design"
cube([200, 60, 10], center=true);
for (x = [-60:60:60]) {
translate([x, 0, 5]) cylinder(h=70, r=4);
}
1 if (mode == "design") {
for (d = [2:1:7]) {
translate([-60, 0, 10 + (7-d)*10]) disc(d*4, 5);
}
}
2 else if (mode == "print") {
for (d = [2:1:7]) {
if (d > 4) {
translate([60*d - 350, 60, 0]) disc(d*4, 5);
}
else {
translate([60*d - 200, 100, 0]) disc(d*4, 5);
}
}
}
module disc(disc_radius, hole_radius) {
difference() {
cylinder(h=10, r=disc_radius, center=true);
cylinder(h=11, r=hole_radius, center=true);
}
}
清单 6-3:使用if语句区分打印模式和设计模式

图 6-3:一个用于打印的汉诺塔项目设置
清单 6-3 使用名为mode的变量和扩展的if语句来决定是以"print"模式还是"design"模式绘制形状。如果mode == "design" 1,圆盘垂直排列,堆叠在一起,便于可视化并检查正确性。如果mode == "print" 2,圆盘被安排在两行水平位置,这对于 3D 打印来说是一个合适的设置。这种区分使得你可以在两种配置之间快速切换。当你准备好打印时,只需要将mode的值更改为"print",设计就会自动改变。
使用随机数作为设计元素
随机数是为设计增添不可预测元素的一种有趣方式,这在设计中有些特征你希望它们相似但又不完全相同时非常方便。例如,你可以轻松地使用随机数生成一座城市的摩天大楼,所有大楼的高度、宽度和窗户数都不同。
当你掷一个六面骰子时,你可以预期其中一个值(1、2、3、4、5、6)会成为掷骰子的结果,但你无法预测具体的结果。类似的过程发生在rands函数中。你可以确信会在指定范围内选择一个小数值,但在语句执行之前你无法确切知道会选择哪个值。
使用数学函数rands来生成随机数。以下代码行选择两个介于 0 和 1 之间的随机小数:
number_list = rands(0, 1, 2);
你传递给rands的第一个参数指定了随机数生成器可以选择的最小小数值。在这个例子中,最小值为 0.0。第二个参数指定了可能的最大数值,在这个例子中是 1.0。第三个参数 2 指定了要选择多少个数字。变量number_list存储了生成的随机数列表,以便稍后使用。
以下代码段从 10 到 20 之间选择三个随机数,然后将这三个数字的列表存储在一个名为number_list的变量中。列表中的每个随机数随后会与number_list变量一起打印到控制台窗口,并在每个数字的位置后面加上方括号([ ])。与大多数编程语言一样,OpenSCAD 将列表中的第一个元素视为位置[0]:
number_list = rands(10, 20, 3);
echo(number_list[0]);
echo(number_list[1]);
echo(number_list[2]);
每次预览这段代码时,你将看到从 10 到 20 之间随机选择的三组不同的小数数值被打印到控制台。
rands函数可以选择你提供的范围内的任何小数,但有时限制设计仅使用整数(即没有小数的数字)会更方便。如果你的设计需要在特定范围内选择一个随机整数,可以使用数学round函数将随机生成的小数映射到整数。round函数根据数字的小数部分决定该数字是四舍五入为上(>=0.5)还是四舍五入为下(<0.5):
number_list = rands(9.5, 20.49, 3);
echo(round(number_list[0]));
echo(round(number_list[1]));
echo(round(number_list[2]));
每次运行这段代码时,你将看到三组不同的整数,从 10 到 20 之间被打印到控制台,因为在每个echo语句中使用了数学round函数。请注意,rands函数的前两个参数已更改为 9.5 和 20.49,以确保原始范围中的每个整数(即 10、11、12、13、14、15、16、17、18、19 或 20)有大致相等的机会被选中。因为我们不希望允许随机选择 20.5 并将其四舍五入为 21,所以我们使用 20.49 作为可以生成的最大值。这会导致 20 被随机生成的概率略低于其他整数值,但差异非常小。
随机数是生成设计元素的一种有用方法,仅在特定百分比的情况下使用。例如,你可以修改前一章中的摩天大楼设计,使得 50%的情况下,摩天大楼的屋顶上会有一个水塔。
清单 6-4 绘制了与清单 6-2 相同的简单摩天大楼设计。这一新版本的设计有时会在建筑物顶部加上一个水塔(图 6-4)。
num_rows = 10;
num_col = 6;
building_width = num_col * 5;
building_height = num_rows * 6;
difference() {
cube([building_width, 10, building_height]);
for (z = [1:1:num_rows]) {
for (x = [0:1:num_col-1]) {
if (z == 1) {
translate([x*5 + 1, -1, -1]) cube([3, 3, 8]);
}
else {
translate([x*5 + 1, -1, z*5]) cube([3, 3, 4]);
}
}
}
}
1 draw_tower = rands(0, 1, 1);
2 if (draw_tower[0] < 0.5) {
translate([building_width/6, 5, building_height])
watertower(building_width/4);
}
module watertower(width) {
$fn = 20;
cylinder(h=5, r=width/2);
translate([0, 0, 5]) cylinder(h=5, r1=width/2, r2=0);
}
清单 6-4:使用if语句和随机数有时绘制一个水塔
在绘制一个基础建筑后,设计会生成一个包含 0 和 1 之间的单个随机数的列表 1。这个列表被存储在draw_tower变量中。一个if语句 2 测试这个随机生成的数字,只有当生成的数字小于 0.5 时,才会在摩天大楼顶部绘制一个水塔。这意味着摩天大楼大约 50%的时间会有水塔,另外 50%的时间则没有水塔。

图 6-4:带有水塔的摩天大楼
接下来,我们使用随机数创建一个随机大小的摩天大楼街区(图 6-5):
1 use <skyscraper.scad>
num_buildings = 5;
2 width_list = rands(10, 30, num_buildings);
length_list = rands(20, 30, num_buildings);
height_list = rands(20, 100, num_buildings);
window_row_list = rands(2.5, 10.49, num_buildings);
window_col_list = rands(2.5, 10.49, num_buildings);
watertower_list = rands(0, 1, num_buildings);
for (n=[0:1:num_buildings-1]) {
3 width = width_list[n];
length = length_list[n];
height = height_list[n];
4 window_rows = round(window_row_list[n]);
window_cols = round(window_col_list[n]);
watertower = round(watertower_list[n]);
translate([0, n*30, 0]) {
5 skyscraper(width, length, height, window_rows, window_cols, watertower);
}
}

图 6-5:一排随机大小的摩天大楼,有些带有水塔
摩天大楼模块是从skyscraper.scad 1 导入的,以保持设计小巧和易于管理。接下来,为摩天大楼模块的每个参数生成一个随机数列表(大小为num_buildings)2。然后,使用for循环根据num_buildings变量指定的值绘制多个摩天大楼。对于每个新的摩天大楼,从每个列表中的适当位置分配随机数给变量 3。对于那些小数值不合适的参数(例如你不希望画出半个窗户),小数会被四舍五入为整数值 4。最后,这些随机生成的值指定了每个新摩天大楼的各种参数 5。每次预览或渲染这个设计时,每栋建筑都会有所不同,因为生成每个摩天大楼所使用的随机值不同。这种技术有助于让重复生成的计算机设计看起来更自然和有机。
总结
本章介绍了通过if语句创建条件分支的概念,这可以让你创建适应变化环境的设计。if语句的每个部分仅在特定条件为真时执行,从而允许你生成具有不同特征的设计。这种多样性使得你能够简洁地描述复杂的设计。
在使用if语句创建动态设计时,请记住以下概念:
-
if语句使用布尔表达式来评估条件是否为真或假。 -
if语句仅在其布尔表达式为true时执行。 -
OpenSCAD 中的所有表达式都会按照运算顺序进行评估,这意味着复杂的布尔表达式可以从内到外进行评估。
-
嵌套的
if语句是一个if语句放置在另一个if语句内部。 -
要表示当布尔表达式为
false时应该发生什么,可以用else语句扩展if语句。 -
你可以在一个扩展的
if语句中结合多个互斥的决策。 -
else语句允许你提供一组默认的语句,在扩展的if语句中的布尔条件都不为真时执行。 -
你可以使用带有随机数的
if语句为你的设计带来自然的有机感。 -
if语句可以帮助你将设计组织成不同的模式(比如"print"或"design"),使得修改重要配置细节变得更加简便。
第七章:设计大型项目

在本章中,你将扩展迄今为止学到的知识,使用 OpenSCAD 构建一个复杂设计。具体来说,你将采用迭代设计周期来规划并完成一个更大的项目。首先,你将应用计算思维来分析和规划你的设计。然后,你将应用流行的步进 骨架方法,从一个基础的抽象设计演变出一个低保真原型,最终转化为一个高度详细的最终设计。使用这种方法,你将首先连接项目的所有主要组件,然后再详细构建每个组件的个别细节。最后一步是填充较小的细节以完成项目。
设计周期
设计周期是一种常见的工作方法,包含四个连续的阶段,帮助开发复杂设计项目的解决方案:
调查
- 明确你想要实现的目标。有哪些重要的考虑因素或限制条件可能会影响你的解决方案?你需要什么才能实现目标?你能想象你要建造的东西吗?
计划
- 将构建解决方案的过程划分为一系列步骤。由于你是使用 OpenSCAD(一种编程语言)进行设计,因此可以在设计周期的这一阶段应用计算思维概念(分解、抽象、发现模式和算法),以确定实现目标的最佳方法。
创建
- 遵循你的计划。创作过程中通常会暴露出新的问题,因此最好先构建大致的解决方案,再集中关注细节。采用步进骨架方法开发复杂设计有助于更轻松地多次重复创建阶段。每次重复的创建阶段(称为设计迭代)都会为整体设计增加更多细节,使你能够首先专注于最重要的结构细节。
评估
- 将创建阶段的每次迭代(你实际构建的内容)与原始问题(你打算构建的内容)进行比较。识别关注点,然后根据需要重复设计周期的任何步骤。
请记住,设计周期的各个阶段更像是一个循环过程。在整个过程中,你可能会多次回到这些阶段,直到对最终设计满意为止。
比萨斜塔模型
让我们按照设计周期来创建意大利著名的比萨斜塔模型(图 7-1)。
本项目的重点是将设计过程与计算思维相结合,因此我们将创建一个可识别的比萨斜塔模型,而不是一个建筑学上精确的比例模型。

图 7-1:比萨斜塔(照片由 Svetlana Tikhonova 提供,依据 CC0 1.0 Universal [CC0 1.0] 公共领域献让许可;在图 7-2 至 7-4 中复制)
第 1 步:调查——定义多个视角
第一步是寻找比萨斜塔的照片,帮助可视化最终设计。我们收集了展示不同视角的图片,以便从各个角度了解这座建筑的样子,包括正面、背面、左侧、右侧和顶部。我们(毫不意外)没有找到底部视图的照片,但我们寻找了能够清楚展示塔楼与地面接触的照片。
设计周期中的调查步骤非常重要,即使你想要建造一些自己发明的东西。如果你找不到你想要建造的确切图片,可以寻找类似的东西。如果运气不好,手工草绘出你预期设计的粗略草图。在编码之前可视化你的设计,将为你节省大量的时间和挫折。关键是,在敲下第一行代码之前,先绘制出你的开发过程图。
步骤 2:规划——应用计算思维
通过对比萨斜塔外观有清晰的了解,你将分析这座建筑,找出可以应用计算思维原理的地方:分解、模式、抽象和算法。在使用 OpenSCAD(或任何其他编程语言)创建设计时,应用这些原理将帮助你更聪明地工作,而不是更辛苦地工作,并使计算机为你完成繁琐的任务。
分解
分解是将一个庞大、复杂的问题拆解成更小、更易描述的子问题的过程,这有助于你在大型项目中识别何时创建模块并将文件分开。分解比萨斜塔的一种方法是将建筑物分为三个不同的部分(底部、中部和顶部),它们都以相同的角度“倾斜”。然后,你可以将这三个部分分解成更小的子组件,如柱子、层级、围栏和拱门(图 7-2)。

图 7-2:使用基本的分解方法将塔楼分解为更小的组件
模式
在设计中寻找模式有点像分解,因为目标是将一个复杂的设计分解成更小、更易于管理的部分。然而,模式的目标是总结元素如何重复出现的过程(图 7-3)。

图 7-3:重复形状的模式
例如,比萨斜塔的中间部分基本上是由相同的形状组重复六次组成的。每个“层级”也都包括围绕其外周重复的拱门/柱子。事实上,底部和顶部部分也包含重复的拱门/柱子(尽管与中间部分的大小和间隔不同)。此外,顶部部分有两道围栏,重复的柱子,以及多种尺寸的重复拱门形状。
抽象
抽象是通过高层次的描述总结较小细节的过程,以便传达全局信息。将比萨斜塔的每个部分表示为一个圆柱体是一种通用的抽象,省略了许多细节(图 7-4)。

图 7-4:比萨斜塔抽象为三个圆柱体的示意图
将三个部分抽象为圆柱体使你能够在考虑较小且不太重要的特征之前,专注于更大的元素(如塔的倾斜角度和每个部分的比例大小)。
算法
由于比萨斜塔的建筑中存在大量重复,创建塔的设计算法需要多个循环。例如,塔周围的柱子涉及一个不断递增旋转角度的循环。循环的柱子出现在三个部分(底部、中部和顶部)中,尽管每个部分包含不同数量的重复柱子,并且大小各异。
比萨斜塔周围不同大小的柱子的多个使用案例表明,使用参数化柱模块是一个合适的算法选择;在模块中包含参数可以让你为塔的每个部分重用相同的基本代码。事实上,这个项目的设计提供了许多机会来在你的代码中使用模块。在项目的分解和模式分析过程中,你确定的每个基本组件都可能成为模块的候选项。在这种情况下,你可以为顶部部分、中部部分、底部部分、层级、柱子、拱门和围栏创建模块。
步骤 3:创建—使用行走骨架方法
设计周期的前两步目标是理解你想要构建的内容,并为将一个大型复杂项目拆解成一系列可管理的部分创建明确的策略。在第 3 步中,你通过使用行走骨架开发过程开始编码,使你能够将设计从粗略的构建块演变为最终的高度详细完成品。你将使用这种方法创建塔的多个版本,在每次设计迭代中进行逐步改进(图 7-5)。

图 7-5:使用行走骨架方法演化比萨斜塔
图 7-5 中顶部、中部和底部的初始版本是最终详细版本的粗略抽象。这些设计的主要部分首先作为建筑骨架连接,然后在一个演变的过程中逐渐完善——因此称之为行走骨架。
步骤 4:评估—决定哪些设计过程步骤需要重复
设计周期的“最终”步骤更多的是一个问题而不是其他。你的设计是否达到了预期的效果?根据答案,决定需要重新审视设计过程中的哪些步骤。
为了解答塔楼示例中的问题,你将通过将渲染的 OpenSCAD 模型与真实的比萨斜塔照片进行对比。事实上,你将在每次步态骨架迭代后应用评估步骤,以决定下一次迭代要添加哪些特征。
步态骨架:构建比萨斜塔
在本章的剩余部分,你将通过一系列设计迭代构建比萨斜塔的多个版本,以展示步态骨架开发过程。每个版本都会增加更多细节,因此你需要将每个迭代与参考照片进行对比,并在过程中重新考虑你的计划和算法。这个方法让你可以将设计周期应用到每个迭代中,而无需过多担心代码的组织或连接方式。
迭代 1:连接塔楼的基本构建块
第一个版本塔楼设计的目标是创建并连接建筑的三个部分:顶部、中部和底部。你还需要加入一个平台以保证稳定性(毕竟塔楼是倾斜的)。
将建筑的整体设计分解为更小的部分,为逐步发展设计提供了基础,因为你可以独立编辑塔楼的各个部分。最初,你将只生成基本的圆柱体,作为每个部分设计的大致近似,因为步态骨架的第一阶段仅专注于连接项目的各个构建块(图 7-6)。

图 7-6:一个具有三个部分的抽象塔
虽然你可以使用一个包含多个模块的非常大的文件,但你将把这些部分分离到独立的文件中(bottom.scad、middle.scad 和 top.scad),并创建一个连接文件(tower.scad)。将代码分布在多个文件中可以让你轻松创建、查找和编辑每个部分相关的模块。你还可以使用这种多文件方法与他人合作,每个人可以同时专注于不同的文件。
第一步中最棘手的部分是考虑设计中不同组件之间如何相互作用。通常,这意味着需要识别每个设计部分绘制所需的关键信息。例如,为了绘制每个部分的抽象圆柱形表示,你至少需要该部分的高度和半径。主要项目文件(tower.scad)将通过模块参数把这些信息传递给每个部分。
由于顶部、中部和底部部分都使用圆柱体作为最终设计的抽象表示,因此首先创建这些文件相对容易。在设计的这个阶段,每个部分的代码看起来非常相似,这也是抽象的另一个优点。你不需要担心小细节,所以你可以在这三个文件中复制粘贴代码,仅进行最小的修改。
bottom.scad 文件定义了一个圆柱体来创建塔的最低部分的简单版本:
// bottom.scad v1
1 module bottom_section(width, height) {
radius = 0.5 * width;
cylinder(h=height, r=radius);
}
tower.scad 文件通过 width 和 height 参数将底部部分的尺寸传递给 bottom_section 模块 1。
接下来,middle.scad 文件定义了中部部分的初步版本:
// middle.scad v1
1 module middle_section(width, height) {
radius = 0.5 * width;
cylinder(h=height, r=radius);
}
再次,tower.scad 文件通过 width 和 height 参数将宽度和高度传递给 middle_section 模块 1。
类似地,top.scad 文件定义了一个基本的圆柱体来表示塔的顶部部分:
// top.scad v1
1 module top_section(width, height) {
radius = 0.5 * width;
2 cylinder(h=height, r=radius);
}
与底部和中部部分一样,tower.scad 文件使用参数为 top_section 模块提供所需的尺寸 1。三个模块中的参数顺序和数量是相同的。这是一个故意的选择,用以简化设计架构。随着设计复杂性的增加,top.scad、bottom.scad、middle.scad 和 tower.scad 之间的一致接口将使得调整每个部分的比例变得更容易。将每个圆柱体的测量值看作结构的半径而非直径 2 也是一个故意的决定(尽管有些任意)。在这一阶段,将宽度作为圆柱体的直径也可以理解。
接下来,我们创建 tower.scad,它提供了必要的尺寸并将塔的三个部分与平台连接起来:
// tower.scad v1
1 use <bottom.scad>
use <middle.scad>
use <top.scad>
2 tower_height = 100;
tower_width = 0.3 * tower_height;
bottom_height = 0.2 * tower_height;
middle_height = 0.65 * tower_height;
top_height = 0.15 * tower_height;
base_width = 2 * tower_width;
base_height = 0.1 * tower_width;
lean_angle = 4;
3 $fn = 20;
4 rotate([lean_angle, 0, 0]) {
color("grey") {
bottom_section(tower_width, bottom_height);
}
color("lightgrey") {
translate([0, 0, bottom_height])
middle_section(tower_width, middle_height);
}
color("white") {
translate([0, 0, bottom_height + middle_height])
5 top_section(tower_width, top_height);
}
}
color("lightgreen") {
6 cube([base_width, base_width, base_height], center=true);
}
tower.scad 文件的第一部分链接到前面描述的三个文件,这些文件定义了塔的顶部、中部和底部部分 1。接下来的部分定义了变量,用于帮助组织塔的重要特性 2。
由于设计不仅包括塔,还包括一个平台来保证稳定性,你创建了变量来组织整体塔的高度和宽度(tower_height 和 tower_width),塔各部分的高度(bottom_height、middle_height 和 top_height),平台的高度和宽度(base_height 和 base_width),以及塔的整体“倾斜”角度(lean_angle)。你最初将 tower_height 变量设置为一个任意值,然后将其作为大多数其他变量定义的一部分。例如,底部部分的高度是 tower_height 变量的 20%,因此如果你想改变整个设计的尺寸,只需改变 tower_height 变量的值。
接下来,您使用相对较少的段数(20)来近似曲线形状,以加速初始设计的渲染过程 3。最后的设计迭代将段数增加到 100,以便在最终设计中生成更平滑的曲面。
为了避免对所有三个部分重复相同的旋转操作,您使用一个操作将一致的旋转角度应用于三个部分 4。每个部分通过相应的模块调用,并调整其宽度和高度的参数。translate操作将中部和顶部部分沿 z 轴移动 5。
最后,您将平台绘制为一个简单的长方体 6。您还为地面和每个部分应用不同的颜色,以表示基本的比例关系。
从这一点开始,您不再需要对tower.scad文件进行重大更改。您最初对每个部分进行正确尺寸设置和定位的努力将构成塔楼设计的建筑“骨架”,而接下来的设计迭代将填补塔楼顶部、中部和底部部分的缺失细节。未来您可能需要对该文件进行的唯一更改是调整参数,以便在设计演变时微调比例,或者更改$fn以提高渲染模型的平滑度。您只需替换数值,而不是编写新的代码语句来进行这些更改。
迭代 2:在中间部分寻找重复
让我们仔细看看塔楼的中部部分(middle.scad)第二次迭代,并运用一些来自规划阶段的计算思维技巧——即分解和寻找模式。在中部部分,相同的形状集合(或层级)垂直重复六次(图 7-7)。

图 7-7:带有循环中间部分的抽象比萨斜塔
图 7-8 显示了其中一个重复的层级形状。

图 7-8:单个层级形状
要创建这些重复的层级,您需要对middle.scad文件进行以下更改:
// middle.scad v2
level(50, 25);
module middle_section(width, height) {
level_height = height / 6;
level_radius = 0.5 * width;
1 for (h=[0:1:5]) {
floor_offset = h * level_height;
translate([0, 0, floor_offset])
level(level_radius, level_height);
}
}
2 module level(level_radius, level_height) {
lip_height = 0.1 * level_height;
inner_radius = 0.7 * level_radius;
overhang_height = 0.3 * level_height;
3 overhang_radius = 0.95 * level_radius;
// lip
translate([0, 0, level_height - lip_height])
cylinder(h=lip_height, r=level_radius);
// overhang
translate([0, 0, level_height - lip_height - overhang_height])
cylinder(h=overhang_height, r=overhang_radius);
// inner structure
cylinder(h=level_height, r=inner_radius);
}
这些更改为中间部分增加了更多的细节,使其不再是一个抽象的圆柱体。level模块 2 组织了构成中间部分每一层的所有形状,for循环 1 为该部分的六层每一层重复创建一个新的level形状。现在,每一层都包括一个延伸到塔全半径的唇缘,一个为柱子提供天花板的悬挑结构,以及一个用于容纳楼梯、门等的内部结构。您创建了几个变量,将每一层特征的大小(lip_height、inner_radius、overhang_height和overhang_radius)与level模块参数(level_radius和level_height)相关联 3。
使用这个重复的level模块,您可以通过在一个地方进行修改,立即同时更新所有六层。例如,如果您想让每一层的边缘稍微厚一点,或者改变悬挑半径以提供更多的柱子空间,您只需对level模块定义进行一次简单的修改。因为在我们步进式构建的过程中,您只是在这一阶段对middle_section模块添加细节,所以middle.scad是您需要更新的唯一文件,用于塔楼设计的第二次迭代。
要在整体设计中看到这些新变化的反映(图 7-7),保存middle.scad文件,然后在tower.scad中预览整个设计。除了使您的设计更改永久生效,保存middle.scad文件还可以让 OpenSCAD 知道您希望其他文件使用更新后的代码。如果您希望单独查看middle_section或level形状,可以在middle.scad顶部创建该形状并预览该文件。您可以在middle.scad中包括一个语句来绘制middle_section或level形状,而不用担心这些形状会自动显示在其他文件中。通过use指令连接另一个文件与middle.scad,意味着来自middle.scad的模块定义将在tower.scad中可用。除非连接的文件使用了middle.scad中的某个模块,否则middle.scad中的任何绘制形状都不会显示。
第三次迭代:为中间部分添加更多细节
在您的计算思维中,接下来要考虑的模式是沿每一层的周围重复柱子和拱门(图 7-9)。

图 7-9:带有重复柱子的楼层
要将这些新模式应用到设计中,您需要创建一个column形状,并沿着level模块的圆周重复该形状。这意味着您需要再次修改middle.scad文件,因为level模块定义就在那个文件中。为了创建一个column形状,您还需要在新的column.scad文件中定义一个column模块。
在设计周期的规划阶段,您注意到柱子和拱门在塔楼的三个部分的周围重复出现。由于您需要在多个文件中包含柱子形状,在一个单独的文件中定义柱子模块,使得不同部分更容易使用该新形状定义。柱子和拱门在每个部分中以不同的模式重复,它们的装饰性也有所不同。因此,在这个初步阶段,您将专注于创建一个具有基本组件的抽象柱子(图 7-10)。然后,您可以在后续设计迭代中更新这个基本的柱子定义。

图 7-10:一个抽象柱子
在一个单独的文件column.scad中创建column模块,可以在未来根据需要更轻松地共享和发展您的柱子使用方式:
// column.scad v3
1 module column(col_width, col_height) {
col_radius = 0.5 * col_width;
2 orn_height = 0.05 * col_height;
translate([-col_radius, -col_radius, col_height - orn_height])
cube([col_width, col_width, orn_height]);
cylinder(h=col_height, r=col_radius);
translate([-col_radius, -col_radius, 0])
cube([col_width, col_width, orn_height]);
}
与其他模块一样,你在column模块 1 中包含了两个参数(col_width和col_height),提供了创建柱形所需的信息。根据柱子的高度和宽度,创建了变量(col_radius和orn_height),用来描述柱子的半径以及柱子顶部和底部装饰的高度 2。虽然这样定义模块可能会使模块的定义变得更复杂,但定义并使用这些变量,而不是将重复的算术运算作为模块参数或操作内部的一部分,可以减少出错的可能性,将所有设计假设集中在模块的顶部,并且更容易更新所有涉及的测量值。
要调用这个新的column模块,你接下来需要修改middle.scad中的level模块,在每一层的圆周上绘制重复的柱子和拱门:
// middle.scad v3
1 use <column.scad>
...
module level(level_radius, level_height) {
2 lip_height = 0.1 * level_height;
inner_radius = 0.7 * level_radius;
overhang_height = 0.3 * level_height;
overhang_radius = 0.95 * level_radius;
num_cols = 24;
angle_size = 360 / num_cols;
col_height = 0.65 * level_height;
col_width = 0.2 * col_height;
arch_depth = 2 * (level_radius - inner_radius);
// lip
translate([0, 0, level_height - lip_height])
cylinder(h=lip_height, r=level_radius);
translate([0, 0, col_height]) {
difference() {
// overhang
cylinder(h=overhang_height, r=overhang_radius);
// arches
3 for (i=[0:1:num_cols-1]) {
angle = i * angle_size + angle_size/2;
rotate([0, 0, angle])
translate([inner_radius, 0, 0])
rotate([0, 90, 0])
cylinder(h=arch_depth, r=col_width, center=true);
}
}
}
// inner structure
cylinder(h=level_height, r=inner_radius);
// columns
4 for (i=[0:1:num_cols-1]) {
angle = i * angle_size;
rotate([0, 0, angle])
translate([overhang_radius - 0.5 * col_width, 0, 0])
column(col_width, col_height);
}
}
将这个更新版本的middle.scad与第二次设计迭代中的版本进行比较,揭示了level模块中的三大新增内容。首先,column.scad通过use指令与此文件 1 连接,以便你可以在该文件中使用新的column模块来绘制柱子形状。接下来,定义了描述每层柱子数量(num_cols)、柱子沿塔圆周重复的角度(angle_size)、每个柱子的宽度和高度(col_width和col_height)以及从每层悬挑雕刻的拱门深度(arch_depth)的变量 2。
在创建悬挑后,你在差异操作中包含了一个for循环,用来在每个柱子位置之间雕刻出拱门 3。一个最终的for循环在层的圆周上重复绘制柱子 4。你也可以将这两个循环合并成一个单一的for循环,并使用if语句;然而,为了让逻辑更加清晰,这里将它们分开。
如前所述,为了在整体设计中看到这些新的变化,保存middle.scad和column.scad;然后在tower.scad中预览整个塔的设计。如果只想查看没有其他塔身部分的中部部分,在middle.scad的顶部添加一个语句来绘制middle_section形状;然后在middle.scad中预览设计。你还可以通过在column.scad的顶部添加一个语句来绘制column形状,然后在该文件中预览设计,这样也能轻松查看单独的柱子形状。
在使用相对较少的代码向中部部分添加大量重复的柱子和拱门后,塔的这一部分(图 7-11)现在与我们参考的比萨斜塔照片(图 7-1)更加相似。
然而,正如你在 图 7-11 中看到的,顶部和底部部分仍然是抽象的简化设计。在每次迭代后的设计周期评估步骤有助于识别缺失的细节,这些细节可能为设计提供最显著的改进。完成这一迭代后,你应该再次参考照片(图 7-1),决定塔楼的哪一部分现在最需要改进。

图 7-11:比萨斜塔带有模块化柱子
迭代 4:为顶部部分添加细节
塔楼顶部缺少围栏、重复的柱子和拱门(窗户和门),因此下一次迭代将集中在添加这些细节。你将为顶部部分添加两个围栏,并且会添加不同大小和高度交替的拱门(图 7-12),因此你将通过添加 fence 模块和 archway 模块来修改 top.scad。你将以不同的尺寸绘制 archway 模块,创建我们参考照片顶部部分中显示的门和窗户(图 7-1)。

图 7-12:带有不同大小交替拱门的围栏顶部部分
更新后的 top.scad 文件将围栏和拱门的细节添加到塔楼的顶部部分:
// top.scad v4
module top_section(width, height) {
1 top_radius = 0.4 * width;
room_radius = 0.75 * top_radius;
num_doors= 5;
door_angle= 360 / num_doors;
overhang_height = 0.1 * height;
overhang_width = 1.1 * top_radius;
door_height = 0.6 * height;
door_width = 0.35 * height;
window_height = 0.25 * height;
window_width = 0.15 * height;
// overhang
translate([0, 0, height - overhang_height])
cylinder(h=overhang_height, r=overhang_width);
//inner structure
difference() {
cylinder(h=height, r=top_radius);
translate([0, 0, 1]) {
cylinder(h=height-2, r=room_radius);
2 for (i=[0:1:num_doors-1]) {
angle = i * door_angle;
rotate([0, 0, angle])
translate([top_radius-2, 0, 0.25*height])
// doors
archway(door_height, door_width, room_radius);
rotate([0, 0, angle+0.5*door_angle])
translate([top_radius - 2, 0, 0.6*height])
// windows
archway(window_height, window_width, room_radius);
}
}
}
//fencing
translate([0, 0, height])
fence(15, 3, top_radius, 1);
3 fence(20, 3, 0.5*width, 1);
}
4 module fence(num_posts, fence_height, fence_radius, post_width) {
post_radius = 0.5 * post_width;
angle_size = 360/num_posts;
ring_height = 0.5;
post_height = fence_height - ring_height;
translate([0, 0, post_height])
ring(fence_radius - post_width, fence_radius, ring_height);
translate([0, 0, post_height / 2])
ring(fence_radius - post_width, fence_radius, ring_height);
for (i=[0:1:num_posts-1]) {
angle = i * angle_size;
rotate([0, 0, angle])
translate([fence_radius - post_radius, 0, 0])
cylinder(h=post_height, r=post_radius);
}
}
5 module ring(inner_radius, outer_radius, height) {
difference() {
cylinder(h=height, r=outer_radius);
translate([0, 0, 1])
cylinder(h=height+2, r=inner_radius, center=true);
}
}
6 module archway(height, width, depth) {
radius = 0.5 * width;
rotate([90, 0, -90]) {
translate([0, (height - radius) / 2, -depth / 2])
cylinder(h=depth, r=radius);
cube([width, height - radius, depth], center=true);
}
}
和其他模块定义一样,你首先定义变量来描述顶部部分的各种特征 1。窗户的数量基于门的数量(num_doors),但其他部分,你刻意选择了自文档化的变量名。一个包含在 difference 操作中的 for 循环从顶部部分的内部结构中减去重复的窗户和门 2。窗户和门形状相似,因此你定义了一个单独的 archway 模块,通过 height、width 和 depth 参数来改变窗户和门形状的大小 6。
top_section 模块通过绘制两个围栏形状来结束 3。这些围栏基本相同,但尺寸不同,因此你定义了一个 fence 模块来构建它们 4。你还包含了一个 ring 模块,以便更容易创建各种围栏环 5。ring 模块的定义是从之前的设计活动中转移过来的(见第五章)。重复使用以前项目中的模块可以节省大量时间和精力。
为了简化项目的组织,你只在 top.scad 文件中包含了 fence、ring 和 archway 模块,因为其他部分没有这些形状。与之前的设计迭代一样,保存你对 top.scad 的更新;然后预览设计,以便查看这些更改在其他文件中的效果。
top_section 模块现在生成了塔楼顶部的更详细版本(图 7-13)。

图 7-13:带有交替拱门的围栏顶部部分,详细视图
将这个设计迭代与塔楼的参考照片进行比较(见图 7-1),你的评估表明底部部分现在需要最多的关注。
迭代 5:为底部部分添加细节
此更新修改了bottom.scad文件,加入了主要缺失的特征(柱子和拱门):
// bottom.scad v5
1 use <column.scad>
module bottom_section(width, height) {
radius = 0.5 * width;
inner_radius = 0.9 * radius;
lip_radius = 1.05 * radius;
lip_height = 0.05 * height;
overhang_height = 0.2 * height;
num_cols = 14;
angle_size = 360 / num_cols;
col_height = height - overhang_height;
col_width = 0.1 * col_height;
// lip
translate([0, 0, height - lip_height])
cylinder(h=lip_height, r=lip_radius);
// inner structure
cylinder(h=height, r=inner_radius);
// columns
2 for (i=[0:1:num_cols-1]) {
angle = i * angle_size;
rotate([0, 0, angle])
translate([radius - 0.5*col_width, 0, 0])
column(col_width, col_height);
}
// arches
translate([0, 0, col_height])
difference( ) {
// overhang
cylinder(h=overhang_height, r=radius);
// arches
3 for (i=[0:1:num_cols-1]) {
angle = i * angle_size + angle_size/2;
rotate([0, 0, angle])
translate([inner_radius, 0, 0])
rotate([0, 90, 0])
cylinder(h=radius-inner_radius, r=col_width);
}
}
}
你首先引入了column.scad,以便访问column模块 1。这允许你使用for循环在底部部分的周围绘制柱子 2。底部部分的柱子比中间部分的柱子要大,因此绘制柱子的参数进行了相应的调整。接着你添加了拱门,同样使用for循环 3。
保存bottom.scad文件,然后预览设计,查看塔楼底部部分的新细节(见图 7-14)。

图 7-14:更新后的底部部分塔楼
现在,塔楼在视觉上已经非常接近实际的比萨斜塔。你可以再次进行评估阶段,但如果你打算制作一个小型的 3D 打印模型,添加更多细节可能不会带来太大好处。
设计周期的最终评估
在这个阶段,塔楼看起来非常类似于比萨斜塔。对tower.scad中的$fn进行轻微修改,可以增加设计的平滑度,使其与真实比萨斜塔更加相似(见图 7-15)。

图 7-15:使用$fn=100而不是$fn=20的更平滑塔楼
你把最小的细节留到最后,这是“行走骨架”开发方法的一个故意特点。每次设计迭代都集中在一个主要领域,特别选择该领域来提供最明显的整体塔楼设计改进。如前所述,由于你打算 3D 打印这个模型,你省略了特别小的细节,但本可以包括以下内容:
-
顶部部分缺失的柱子和拱门。
-
中间和底部部分缺失的矩形门口。
-
每个部分的柱子和拱门的不同装饰。
-
柱子不是简单的圆柱形,你本可以将柱子的顶部半径设为比底部小。
我们提到这些缺失的特征,作为想要继续进行此模型设计迭代的读者的潜在练习。更大的 3D 打印可能会揭示出这些较小的设计特征。
设计组织概览
在你的第一次设计迭代中,你将建筑分为三个低保真度的部分,每个部分都有一个单独的.scad文件。这样,你只需要预览一个文件(tower.scad),因为该文件将三个文件连接在一起。图 7-16 展示了初始项目的组织结构,这样可以减少每个文件中的代码量,便于查找和修改特定部分。

图 7-16:比萨斜塔项目的初步架构
在整个设计过程中,你使用了分解技术来寻找将塔的更大组件拆分成更小部分的机会。在你最后一次迭代后,项目的组织结构发展成包含许多模块和一个额外文件的形式(图 7-17)。这个最终的项目组织展示了“走动骨架”开发方法的主要原则。你的初始项目组织侧重于连接项目的大块部分,而最终的组织结构则展示了你在每次迭代中逐步增加的小细节。
这里描述的组织和开发过程只是构建此项目的一种方式。除了将项目组织成不同集合的独立.scad文件(甚至是一个巨大的.scad文件)外,你还可以创建不同的模块集,将塔分解为更小的构建块。
我们也错过了几个通过加入额外的if语句或for循环来减少重复代码的机会。例如,你本可以创建一个单独的column_ring模块,以“提取”塔周围的柱子和拱门的包含内容。通过谨慎使用if语句和参数,你可以利用column_ring模块绘制三部分中的柱子和拱门,从而大大简化top_section、middle_section和bottom_section模块中所需的代码。

图 7-17:比萨斜塔的最终架构
设计可以随着时间的推移演变,而不会对整体项目的组织结构造成重大变化。你不需要在项目开始时就知道所有需要创建的模块或文件;你可以在对自己正在构建的内容有更好理解后再做决定。每次应用设计周期的评估阶段时,你都有机会重新考虑需要对设计做出的更改。
总结
本章介绍了在构建复杂项目时,故意遵循设计周期的好处。你应用了计算思维来指导规划阶段,并采用了“走动骨架”方法将构建和评估阶段结合成一个循环过程。你首先连接了设计中最重要的特性,然后逐步开发每个组件的主要特性。直到开发的最后阶段,你才考虑更小、更细致的细节。
总结一下,在设计复杂项目时,请牢记以下概念:
-
画出你想要构建的项目草图,并用模式、抽象和分解来标注它,帮助你理解如何组织代码。
-
描述绘制新形状所需的最少信息可以帮助你理解新模块可能需要哪些参数。
-
使用自文档化的命名约定将有助于通过揭示每个新变量或模块的目的来组织你的代码。
-
使用颜色来帮助组织不断发展的设计中的不同部分。
-
在进行任何更改时,请确保保存单独的文件,这样其他文件才能使用该文件的最新版本。
-
首先连接项目中最重要的部分,即使这些部分是大致的抽象概念。
-
在步态框架开发方法的最后阶段,设计项目的最小细节。
设计周期和步态框架开发模型是常见的方法,您可以在线找到大量资料供进一步阅读。在使用 OpenSCAD 创建新设计时,我们鼓励您进一步探索这些概念。
第八章:后记

阅读完本书后,特别是如果你参与了设计时间和大型项目练习,你应该已经牢牢掌握了如何使用 OpenSCAD 编程语言创建 3D 打印设计。最后,我们将提供一些有用的建议,指导你接下来该去哪里,并帮助你理解 OpenSCAD 如何融入开源和创客运动的更大生态系统。
了解更多关于 OpenSCAD 的信息
我们在这里已经涵盖了 OpenSCAD 大部分可用功能;然而,仍有更多高级功能等待你去发掘。各种资源可供你解锁 OpenSCAD 的全部创意力量:
访问 OpenSCAD 在线文档
一旦你准备好进一步提升你的 OpenSCAD 技能,你的第一站应该是官方在线 OpenSCAD 文档(openscad.org/documentation)。这是寻找其他结构良好的学习指南的地方,帮助你深入了解 OpenSCAD。你将找到教程、用户手册、更完整的语言参考以及定期更新的链接,包含许多其他学习材料,包括书籍、文章和视频。
重新混合他人的 OpenSCAD 设计
对于稍微不那么结构化的学习,尝试重新混合现有的 OpenSCAD 设计。学习阅读其他人编写的代码,能显著提升你的编码和组织能力。你可以轻松在线搜索 OpenSCAD 设计,并务必查看两个最受欢迎的 3D 设计共享网站:thingiverse.com/ 和 youmagine.com/。搜索openscad将会找到成千上万的 OpenSCAD 设计供你使用和混合。
大多数设计也可以作为 OpenSCAD 代码提供,你可以探索它们,了解其他人是如何在代码中解决具有挑战性的设计问题的。通过将你自己的创新整合到他们的代码中,重新混合他人的设计,是展示你真正理解他们设计中各个部分如何组合的好方法。
加入 OpenSCAD 社区
与 OpenSCAD 设计师社区中的志同道合者互动,是补充你学习的另一种方式。有时候,你的设计想法可能会提出一些独特的挑战,无论阅读或搜索多少,都难以找到答案。向 OpenSCAD 社区寻求帮助,可能会提供完美的解决方案。
官方 OpenSCAD 社区页面 (openscad.org/community) 提供了一个聊天室、邮件列表和论坛,OpenSCAD 用户可以在这里讨论项目、寻求帮助,甚至促进 OpenSCAD 本身的发展。OpenSCAD 是开源软件,开发讨论通常会在社区成员聚集的同一论坛上进行。除了可以找到解决你最困扰的设计问题的答案,参与 OpenSCAD 论坛意味着你也可以帮助他人,甚至通过建议新功能或报告 bug 来影响 OpenSCAD 软件本身的发展。
开源精神
正如我们在本书中多次提到的,OpenSCAD 是开源软件。专有的 3D 设计软件通常价格昂贵,并且有陡峭的学习曲线。即便是“免费”的基于网页的 3D 设计工具,通常也需要创建账户,这可能引发关于隐私或服务持续性的问题。OpenSCAD 的开发者社区希望创造一个真正免费且易于访问的 3D 建模平台,向每个人,尤其是对编程与 3D 设计交叉领域感兴趣的人开放实体 CAD 建模的世界。成百上千的人捐出了他们的时间和精力,以便为你创建和改进 OpenSCAD,期望通过消除这些传统的障碍,鼓励更多的人学习并利用 3D 建模来解决大小问题。
动机与生态系统
为什么这么多人会花费如此多的时间和精力,将传统上“困难”和“昂贵”的东西转变为既免费又更加易于访问和接近的形式?让 OpenSCAD 成为开源的背后有一些重要的动机,包括以下几点:
-
支持庆祝跨文化和跨学科探索的社区
-
支持并参与包容性教学、学习和分享重要的 STEM/STEAM 技能
-
鼓励个人与他人分享他们工作和努力的成果
-
通过提供基于群体的迭代设计过程,使个人能够通过对现有创作的拥有权来推动事物变得更好
-
相信将成果回馈他人会激励更多人也回馈他人,从而对社会产生更大的积极影响
事实上,OpenSCAD 开源项目的存在也得益于陌生人的善意。OpenSCAD 开发社区依赖于许多其他开源项目,这些项目的创建目的是让他人使用这些技术(希望)让世界变得更好。以下是一些最突出的例子:
-
使用 Qt 帮助构建 OpenSCAD 用户界面 (
qt.io/) -
使用 CGAL 帮助评估构造实体几何(CSG)在 OpenSCAD 设计渲染时的表现 (
cgal.org/) -
OpenCSG 与 OpenGL 帮助生成 OpenSCAD 设计的 CSG 预览(
opencsg.org/和www.opengl.org/) -
Boost 提供丰富的 C++便利库(
boost.org/) -
Eigen 提供快速且经过充分测试的线性代数函数(
eigen.tuxfamily.org/)
我们要感谢 OpenSCAD 的开发者以及每一个开源项目的贡献者,感谢他们的时间和宝贵贡献。
网络公民身份
很容易忘记,你通过屏幕访问互联网时,屏幕的另一端是真正的人。开源软件运动在很大程度上依赖于网络公民身份的理念,确保互联网的分布式社交网络能够促进积极的社会变革,同时支持人权的进步。以下是一些关于网络公民身份的起始原则,我们希望你在继续使用 OpenSCAD 和其他开源软件项目的过程中,能始终铭记:
给予应得的荣誉
当你使用别人创作的东西时,请注明出处。这有助于支持原创作者(即使只是赞扬)并表明你意识到“站在巨人的肩膀上”的特权。
对他人保持同理心
记住,你在网上互动的对象不一定与你拥有相同的背景、语言、文化或内在笑话。在所有社区空间中,保持并展示一种尊重和体贴的沟通方式。尊重你所创造的事物对文化和环境的影响。
传递善意
创造能够帮助解决实际问题的事物。分享你的创作,特别是当你通过使用其他人免费提供的工具时所创作的内容。
OpenSCAD 与创客运动
如果忽略 OpenSCAD 与创客运动的关系,那将是一个失误。创客已成为一个日益流行的术语,用来描述采取创意、DIY 方式解决问题的做法。创客通常包括通过使用迭代设计过程以及各种机器、工具和材料来尝试解决问题:纸板原型制作、3D 打印、激光切割、电子学、焊接、木工、缝纫、CNC(计算机数控)加工、乙烯基切割、丝网印刷、水刀切割等等。
OpenSCAD 是创客社区的一个关键软件工具。虽然本书主要集中于使用 OpenSCAD 进行 3D 打印设计,但 3D 打印仅仅是创客社区利用 OpenSCAD 所创造的成果的一部分。将 OpenSCAD 与 3D 打印结合是解决许多问题的绝佳方案,但它并不总是最好的解决方法。培养一种对创客领域内设计工具和范式的全面而深入的理解,会带来许多好处。
创作与创造性问题解决
我们特意使用了设计这个词来描述 OpenSCAD 创作,因为每一个 OpenSCAD 项目都是为了一个特定的原因而创建的,通常是为了解决现实世界中的物理问题。设计的基本概念是问题解决的实践。就像游泳一样,通过设计解决问题是一项真正只有在“水中”时才能学习的技能。每次完成一个 OpenSCAD 项目,你都会提高自己设计特定问题解决方案的能力。
创客运动正确地认为通过设计进行创造性问题解决是一项可转移的技能。如果你是创客运动的新手,你可能会惊讶地发现,设计一个缝纫工具包可以帮助你更好地创建 OpenSCAD 代码的有序序列,或者制作多层丝网印刷可以帮助你将复杂问题分解成定义明确的小部分。你可以在任何媒介中获得这些更高层次的设计技能。除了你在本书中学到的可转移的编程和 3D 打印技术,我们希望你会考虑将你的新问题解决和设计能力应用于其他一些有趣的方向。
2D 制造
2D 制造的世界是应用你在本书中所学技能的广阔领域。将 2D 影像拉伸为 3D 设计是一个强大的 3D 设计工具。然而,许多创客工具使用 2D 文件(如.svg或.dxf)来制造其设计的物理版本。2D 制造机器(如激光切割机、乙烯基切割机、水刀切割机等)本质上是将 2D 形状的轮廓切割成木材、金属、乙烯基、毡子、纸板或大多数其他平面材料的平面片。由于 OpenSCAD 使得使用变量、算术、循环和if语句来放置和组合形状变得非常容易,许多创客使用 OpenSCAD 专门为这些机器创建纯 2D 设计。
这里有一些想法,可以激发你用 OpenSCAD 进行 2D 创意设计:
-
使用一组循环生成你在 OpenSCAD 中创建的 2D 皮革缝纫图案周围的小圆孔。然后,用激光切割机或切割机将图案切割出来。皮革很难用针穿透,但使用 OpenSCAD 循环来生成孔洞将帮助节省时间和精力。
-
使用 CNC 木材切割机将你用 OpenSCAD 2D 形状设计的平板家具制作成真实大小的版本。尽管 3D 打印机的打印区域相对较小,但 CNC 切割机可以切割出相当大的表面面积。3D 打印机可以用于原型制作,而可用的家具则是在大型 CNC 机器上制作的。
-
在你用 OpenSCAD 设计并拉伸出的 2D 齿轮的几个原型版本 3D 打印之后,用水刀切割机将其从金属中切割出来。塑料齿轮的使用寿命远不如金属齿轮,特别是当你实际上将它们用于你的自行车时。
物理计算
许多互动制作项目将电子技术和计算机与其他物理组件相结合,创造出具有动态特性的事物。如果你的 OpenSCAD 设计能够感知并响应世界,甚至能够移动,怎么样?有许多价格便宜、口袋大小的计算机可以大幅提升你使用 OpenSCAD 创建的设计的互动性。
这些微型计算平台利用多种传感器和输出设备(如麦克风、温度传感器、运动传感器、扬声器、马达和 LED)与现实世界互动。以下是一些最受欢迎的小型计算平台:
-
树莓派 (
raspberrypi.org/) -
Arduino (
arduino.cc/) -
micro:bit (
microbit.org/) -
Circuit Playground (
learn.adafruit.com/introducing-circuit-playground/)
每个这些设备都有一个庞大的在线社区,提供丰富的学习资源。将 OpenSCAD 与这些便宜的口袋大小计算机结合使用,可以探索诸如机器人学、物理计算、可穿戴计算、人机交互或物联网等领域。以下是你可能使用 OpenSCAD 和这些设备之一创建的一些项目示例:
-
自动植物/花园浇水系统
-
完全新的互动数字仪器
-
个人、多节点数据中心的物理外壳
-
帮助残障人士提高可访问性的个人辅助设备
通过使用 OpenSCAD 为这些电子设备发明创造性的新用途,你正在让你的项目更容易定制、共享和扩展。也许你甚至可以创造出一些东西,启动你自己的开源项目。
创客空间
创作可以在任何地方进行,但聚集在一个中心地点,让志同道合的创作者们共享想法并解决问题,已成为创客们的流行做法。创客空间是一个物理地点和创客社区,提供一系列工具、机器和学习资源。创客空间提供了你可能无法个人拥有的设备,并作为一个创客的物理社区,能提供与前述虚拟社区相同的好处。你可以在图书馆、学校、独立场地以及创客集会或节日活动中找到创客空间(无论大小、免费或收费) (makerfaire.com/)。
如果你所在的本地社区还没有提供集中共享的创作场所,一些创客空间已经转为虚拟形式。许多在线供应商允许你上传 3D 打印或 2D 切割设计,使用各种材料,为你提供一个经济实惠的起步平台,当你没有访问制造机的权限时,可以创建你设计的物理版本。
更多实践的最终想法
我们想给你一些最后的建议。掌握任何技能的关键在于学习与实践的结合。如果你仅仅阅读了这本书,而没有实际进行编码或设计,那你错过了大量学习的机会,但现在还不晚!你可以现在放下这本书,回到任何一个项目中去。
如果你在寻找更多定义明确的设计练习示例,下面的截图展示了 OpenSCAD 社区中的一些视觉创意,这些创意应该能很好地作为“下一步”项目的灵感。你还可以访问openscad.org/gallery查看更多精心挑选的示例。
可定制量勺
创建一个量勺模块是一个很好的中级设计项目(图 1)。该项目的主要挑战是创建一个包含勺子尺寸、单位和配置(嵌套堆叠勺子或平放的勺子)作为参数的单一 OpenSCAD 模块。然后,可以根据这些参数生成量勺的形状和标签。你能生成具有准确测量值的 3D 打印量勺,确保在烹饪或烘焙时信赖这些测量吗?

图 1:一个集合,包含多个尺寸和配置的量勺
这些量勺是通过最初由 charliearmorycom 设计的 OpenSCAD 代码生成的。你可以在www.thingiverse.com/thing:51874/找到可定制量勺项目。
可定制吸尘工具
设计一个定制吸尘器吸嘴以适配吸尘管的末端是一个很好的例子,展示了如何与现有物理工具接口。创建一个完美契合的物理连接需要仔细测量,并通过 3D 打印机不断实验,以便完善尺寸。此外,这个项目还提供了一个创建可定制喷嘴的机会,可以通过一个或多个模块参数生成(图 2)。

图 2:一个集合,包含带有参数化喷嘴的吸尘器吸嘴
用于生成这些定制吸尘器吸嘴的 OpenSCAD 代码最初由 Ziv Botzer 设计。可定制吸尘工具项目可以在网上找到,网址为www.thingiverse.com/thing:1571860/。
可定制花盆
使用 OpenSCAD 创建花盆模块将使你能够设计出兼具装饰性和功能性的物品。这个中级项目将允许你根据你想要容纳的植物大小,调整 3D 打印的尺寸,无论是大号还是小号(图 3)。这个项目有多个可以调整的参数,并且挑战还在于从同一个模块中生成花盆和托盘。别忘了在花盆底部留一个孔,让水能够流入托盘!
用于生成这系列花盆的 OpenSCAD 代码来自 Robert Wallace 的可定制花盆(经典风格)项目,可以在www.thingiverse.com/thing:2806583/找到。

图 3:不同大小和形状的花盆和托盘的集合
抽屉盒
记得你在第二章中创建的作为“大项目”的桌面整理器吗?这个盒子和抽屉整理器是一个更复杂的整理器概念,可以往很多方向发展。最初的挑战是调整盒子的尺寸,使抽屉既能顺利又牢固地滑入盒子中。定制盒子、抽屉以及抽屉布局的尺寸和设计(理想情况下通过参数化模块)也是未来的良好挑战。注意这个项目如何包括有用的细节,比如盒子隔板上的小球形凸起,以保持抽屉的位置,以及盒子三面上的循环生成孔,以减少 3D 打印抽屉盒所需的时间和材料(图 4)。

图 4:具有几种不同抽屉配置的桌面整理器
Gian Pablo Villamil 的抽屉盒项目可以在www.thingiverse.com/thing:421886/找到。
实验室夹具
该项目专为物理教室设计,是使用 3D 打印制造通常因成本过高而无法购买的物品替代品的一个很好的例子。设计与现有工具或部件相匹配的 3D 打印机械部件总是具有挑战性的。在这个例子中,一系列夹具和支架设计用于与金属螺栓配合使用(图 5)。设计一个合适的内结构以与这些螺栓牢固配合,可能需要一些实验。像这样的项目很好地展示了 OpenSCAD 和 3D 打印如何作为服务项目,为学校或社区中心共同合作。

图 5:物理实验用夹具和支架的集合
实验室夹具项目由 Mark Schober 创建。你可以在www.modelingscience.org/post/3d-print-your-own-lab-clamps/找到用来生成这张图片中夹具的代码(以及如何将金属螺栓和使用硅胶模具批量制造这些部件的更多细节)。
国际象棋棋盘
设计国际象棋棋盘是艺术家和 3D 打印爱好者的最爱项目。虽然所展示的例子非常接近经典的国际象棋棋盘(图 6),并且可能需要寻找一个马头的 3D 模型,但网上存在许多设计可以用来制作更现代或抽象的国际象棋棋盘。创建一个基础模块有助于为你的国际象棋棋盘提供一致的尺寸和设计,而为每个棋子创建一个单独的模块则能使你的 3D 打印更易于组织。

图 6:一个自定义棋盘
用于生成这个棋盘的代码是由 Tim Edwards 设计的,可以在www.thingiverse.com/thing:585218/上找到。
Pegboard Wizard
你是否曾经需要使用 pegboard 来整理工具或硬件?最后一个例子利用标准 pegboard 的模块化潜力,创建了一个包含有用容器盒和工具架的库(图 7)。可以创建一个包含多个参数的单一模块,也可以创建一个包含较少参数的模块集合。无论哪种方式,这个项目都会考验你在应用计算思维原理的同时,如何为你的离线工具包创造一个实用的组织解决方案。

图 7:由 pegboard wizard 创建的一系列 pegboard 收纳工具
Pegstr - Pegboard Wizard 是由 Marius Gheorghescu 设计的,可以在www.thingiverse.com/thing:537516/上找到。
第九章:A
OpenSCAD 语言参考

本语言参考提供了对大多数 OpenSCAD 功能的简短描述,作为快速提醒,帮助使用本书中描述的功能或发现新的 OpenSCAD 特性。请参考官方 OpenSCAD 文档 openscad.org/documentation 获取完整手册。
语法
-
使用一组参数创建一个 2D 或 3D 形状。用分号(
;)结束命令:shape(...); -
创建一个已通过一系列操作转换的形状。用分号(
;)结束语句:transformation2(...) transformation1(...) shape(...); -
创建一个变量来命名和引用一个重要的值;值一旦赋值后无法更改:
var_name = value; -
创建一个名为
name的用户定义形状,具有零个或多个参数。用户定义的形状与内建形状的工作方式相同:module name(...) { ... } name(...); -
创建一个名为
name的用户定义数学运算,具有零个或多个参数:function name(...) = ...; name(...); or name = function(...) ...; name(...); -
导入并立即执行 filename.scad 中的 OpenSCAD 代码:
include `<filename.scad>` -
导入并使(但不立即执行)filename.scad 中的 OpenSCAD 函数和模块可用:
use `<filename.scad>`
运算符
运算符按优先级降序排列。当多个相同优先级的运算符出现在表达式中时,运算符按出现顺序(从左到右)进行计算:
-
^ -
*,/,% -
+,- -
<,>,<=,>= -
==,!= -
&& -
||
2D 形状
-
绘制一个指定半径或直径的圆:
circle(`radius` | d=`diameter`) -
绘制一个边长为 size 的正方形,宽度 = size,高度 = size(相等边长);可选择将正方形居中于 (0,0):
square(`size`, `center`) -
绘制一个矩形,宽度沿 x 轴,长度/深度沿 y 轴,由向量定义;可选择将正方形居中于 (0,0):
square([`width`, `height`], *center*) -
绘制一个连接所有由 [x, y] 点向量定义的点的多边形:
polygon([*[*`x1`, `y2`*]**,* *[*`x2, y2`*]**, ...,* *[*`xn, yn`*]*]) -
绘制一个连接所有由 [x, y] 点向量定义的点的多边形;可选择定义一个包含有孔多边形路径的集合:
polygon(*[*`points`*]*, *[*`paths`*]*) -
绘制由 text 字符串定义的文字;可选择指定文字的大小、字体、水平对齐、垂直对齐、字母间距、方向、语言和脚本:
text(`text`, *size*, *font*, *halign*, *valign*, *spacing*, *direction*, *language*, *script*) -
导入一个 2D SVG 或 DXF 文件:
import("`filename.svg`")
3D 形状
-
绘制一个以 (0, 0, 0) 为中心,指定半径或直径的球体:
sphere(`radius` | d=`diameter`) -
绘制一个立方体,长度 = size,宽度 = size,高度 = size(相等边长);可选择将立方体居中于 (0,0,0):
cube(`size`, *center*) -
绘制一个由向量定义的宽度沿 x 轴,长度/深度沿 y 轴,高度沿 z 轴的立方体;可选择将立方体居中于 (0,0,0):
cube([`width`, `depth`, `height`], center) -
绘制一个指定高度和半径或直径的圆柱体;可选择将圆柱体居中于 (0,0,0):
cylinder(*h*, *r*|*d*, center) -
绘制一个指定高度和半径或直径的圆锥体;可选择将圆锥体居中于 (0,0,0):
cylinder(*h*, *r1*|*d1*, *r2*|*d2*, center) -
绘制一个由点和面向量定义的 3D 实体;可选择使用凸度来改进复杂凹形的预览:
polyhedron(*[*`points`*]*, *[*`faces`*]*, convexity) -
导入一个 STL、OFF、3MF 或 AMF 文件:
import("`filename.stl`") -
绘制数据文件的 3D 高度图;可选地将形状居中于 (0,0) 并使用凸性来改善复杂凹形状的预览:
surface(file = "`filename.dat`", center, convexity)
布尔操作
-
将多个形状组合成一个形状:
union() { ... } -
从初始形状中减去一个或多个形状:
difference() { ... } -
绘制多个形状的重叠区域:
intersection() { ... }
形状变换
-
根据 2D 或 3D 向量平移形状:
translate([`x`, `y`, `z`]) -
根据向量定义的角度围绕每个轴旋转形状:
rotate([`x`, `y`, `z`]) -
沿 z 轴旋转形状特定的角度:
rotate(`angle`) -
根据由 2D 或 3D 向量定义的缩放因子缩放形状:
scale([`x`, `y`, `z`]) -
根据由 2D 或 3D 向量定义的维度调整形状大小;可选地使用
auto保持在未指定维度中的对象纵横比:resize([`x`, `y`, `z`], auto, convexity) -
根据通过原点的对称平面垂直向量反射形状:
mirror([`x`, `y`, `z`]) -
用给定的 4 × 4 仿射变换矩阵将所有子元素的几何形状相乘:
multmatrix(`matrix`) -
根据预定义的颜色名称或十六进制颜色值改变形状的颜色;可选地使颜色(半)透明:
color("`colorname` | `#hex`", alpha) -
根据 RGB 或 RGBA 向量改变形状的颜色。向量中的每个值范围从 0 到 1,表示颜色中红、绿、蓝和 alpha 的比例。
color([`r`, `g`, `b`, `a`]) -
按给定的半径(用于圆角)或增量 + 倒角(用于锐角或切角)将 2D 外框向外或向内移动:
offset(`r`|delta, chamfer) -
通过将 3D 形状投影到 xy 平面上创建 2D 形状;当
cut = true时,创建 3D 物体与 xy 平面的交集的 2D 切片;可选地,当cut = true时:projection(cut) -
在一个或多个形状周围创建凸包:
hull() { ... } -
绘制多个形状的 Minkowski 和:
minkowski() { ... } -
将 2D 形状沿 z 轴挤压成 3D 形状;可选地将形状居中于 (0,0) 或指定挤压的凸性、扭曲、切片和缩放:
linear_extrude(*height*, *center*, *convexity*, *twist*, *slices*, *scale*) -
将 2D 形状围绕 z 轴挤压成具有旋转对称性的固体:
rotate_extrude(angle, convexity)
循环、决策和列表推导
-
根据控制变量的起始、步长和结束(包含)值重复一组形状:
for (`var_name` = [`start`:`step`:`end`]) { ... } -
绘制
for循环生成的所有形状的交集:intersection_for(`var_name` = [`start`:`step`:`end`]) { ... } -
仅在布尔测试为真时执行命令:
if (`boolean_test`) { ... } -
如果布尔测试为真,则执行一组命令;否则,执行备用命令:
if (`boolean_test`) { ... } else { ... } -
根据
for循环生成一个值的列表:`list_var` = [ for (i = `range`|`list`) `func`(`i`) ] -
根据
for循环生成一个值的列表,但仅在该值导致某个条件为真时:`list_var` = [ for (`i` = ...) if (`condition`(`i`)) `func`(`i`) else ... ] -
根据
for循环生成一个列表的列表:`list_var` = [ for (`i` = ...) let (`assignments`) `func`(...) ]
其他形状操作
-
即使在预览模式下也强制生成网格:
render(convexity) { ... } -
在用户定义的模块内,选择由索引、向量或范围指定的子元素:
children(`index` | `vector` | `range`)
修饰符字符
-
*禁用绘制形状。 -
!仅显示特定形状。 -
#将形状高亮为红色用于调试;高亮的形状将被渲染。 -
%将形状高亮为灰色;高亮的形状将不会被渲染。
特殊变量
可写:
-
$fa弧段的最小角度。 -
$fs弧段的最小大小。 -
$fn用于定义弧段的碎片数;忽略$fa和$fs。 -
$vpr视口旋转角度,单位为度。 -
$vpt视口平移。 -
$vpd视口相机的距离。 -
$vpf视口视野。
只读:
-
$t当前动画步骤,归一化为 0 到 1 之间的值。 -
$children模块子元素的数量。 -
$preview如果使用了预览模式,返回真。
数学函数
-
sin(``ANGLE``)计算一个角度的正弦,单位为度。 -
cos(``ANGLE``)计算一个角度的余弦,单位为度。 -
tan(``ANGLE``)计算一个角度的正切,单位为度。 -
acos(``NUMBER``)计算一个数的弧(反)余弦,单位为度。 -
asin(``NUMBER``)计算一个数的弧(反)正弦,单位为度。 -
atan(``NUMBER``)计算一个数的弧(反)正切,单位为度。 -
atan2(``y``,x``)计算两值的弧(反)正切;返回 x 轴和向量[x, y]之间的完整角度(0–360 度)。 -
abs(``NUMBER``)计算一个数的绝对值。 -
sign(``NUMBER``)返回一个单位值,用于提取值的符号。 -
floor(``NUMBER``)计算不大于该数的最大整数。 -
ceil(``NUMBER``)计算下一个更高的整数值。 -
round(``NUMBER``)计算该数的四舍五入值。 -
ln(``NUMBER``)计算一个数的自然对数。 -
exp(``NUMBER``)计算数学常数 e(2.718…)的幂。 -
log(``NUMBER``)计算一个数的以 10 为底的对数。 -
pow(``NUMBER``,NUMBER``)计算一个基数的幂。 -
sqrt(``NUMBER``)计算一个数的平方根。
* `rands(``min``,` `max``,` `count``,` `seed``)` Generates a vector of random numbers; optionally includes the seed for generating repeatable values.* `min(``VECTOR` `|` `a``,` `b``,` `c``)` Calculates the minimum value in a vector or list of parameters.* `max(``VECTOR` `|` `a, b, c``)` Calculates the maximum value in a vector or list of parameters.* `norm(``VECTOR``)` Returns the Euclidean norm of a vector.* `cross(``VECTOR, VECTOR``)` `Calculates the cross-product of two vectors in 3D space.`
```` ## Other Functions 1. `len(``VECTOR``|``STRING``)` Calculates the length of a vector or string parameter. 2. `echo(``STRING``)` Prints a value to the console window for debugging purposes. 3. `concat(``VECTOR,VECTOR,` `...)` Returns a new vector that’s the result of appending the elements of the supplied vectors. 4. `lookup(...)` Looks up a value in a table and linearly interpolates whether there’s no exact match. 5. `str(...)` Converts all parameters to strings and concatenates. 6. `chr(``NUMBER` `|` `VECTOR` `|` `STRING``)` Converts ASCII or Unicode values to a string. 7. `ord(``CHARACTER``)` Converts a character into an ASCII or Unicode value. 8. `search(...)` Finds all occurrences of a value or list of values in a vector, string, or more complex list-of-list construct. 9. `version()` Returns the OpenSCAD version as a vector. 10. `version_num()` Returns the OpenSCAD version as a number. 11. `parent_module(``INDEX``)` ``Returns the name of the module `idx` levels above the current module in the instantiation stack.`` ```* `is_undef(``VARIABLE``)`, `is_list(``VARIABLE``)`, `is_num(``VARIABLE``)`, `is_bool(``VARIABLE``)`, `is_string(``VARIABLE``), is_function(``VARIABLE``)` Returns `true` if the argument is of the specified type.* `assert(``expression``)` Will cause a compilation error if the expression is not true.* `let (``variable` `=` `value``) ...` Assigns a value to a variable only in the following expression.``` ````
第十章:B
OpenSCAD 视觉参考

本附录是绘制、变换和组合本书中涵盖的 3D 和 2D 形状的快速视觉参考。每张截图旁都有一个示例 OpenSCAD 语句,可以用来生成该图像。在某些情况下,我们还包含了一个“阴影”对象,以说明操作之前形状的样子。示例代码语句不会生成这些阴影对象。
3D 基本图形
长方体:
`cube([30, 20, 10]);`

居中长方体:
`cube([30, 20, 10], center=true);`

球体:
`sphere(10);`

平滑球体:
`sphere(10, $fn=100);`

圆柱体:
`cylinder(h=20, r=5);`

锥体:
`cylinder(h=20, r1=5, r2=0);`

居中平滑截头锥:
`cylinder(h=10, r1=3, r2=5, $fn=100, center=true);`

规则棱柱体:
`cylinder(h=5, r=5, $fn=6);`

2D 形状
矩形:
`square([30, 20]);`

居中矩形:
`square([30, 20], center=true);`

圆形:
`circle(10);`

规则多边形:
`circle(10, $fn=5);`

不规则多边形:
`polygon([[0,0], [10,0], [10,10], [5,10]]);`

文本:
`text("hello", font="Sans", size=20);`

组合形状
从形状中减去:
`difference() {`
`sphere(10);`
`translate([0,-15,0]) cube([15,30,15]);`
`}`

从形状中进行多次减法:
`difference() {`
`sphere(10);`
`cube([15, 15, 15]);`
`cylinder(h=15, r=5);`
`}`

两个形状的交集:
`intersection() {`
`cube([10, 10, 10]);`
`cylinder(h=15, r=5);`
`}`

从组合形状中减去:
`difference() {`
`union() {`
`sphere(10);`
`cylinder(h=30, r=5, center=true);`
`}`
`cube([10, 30, 10], center=true);`
`}`

凸包:
`hull() {`
`sphere(10);`
`cylinder(h=20, r=5);`
`}`

闵可夫斯基和:
`minkowski() {`
`sphere(10, $fn=50);`
`cylinder(h=20, r=5);`
`}`

变换
平移:
`translate([5, 10, 0]) cube([5, 3, 1]);`

旋转:
`rotate([0, 0, 60]) cube([30, 20, 10]);`

反射:
`mirror([1, 0, 0]) translate([5, 0, 0]) cylinder(h=1, r=5, $fn=5);`

调整尺寸:
`resize([15, 20, 4]) sphere(r=5, $fn=32);`

挤出 2D 形状:
`linear_extrude(height=10) {`
`polygon([[0, 0], [10, 0],`
`[10, 10], [5, 10]]);`
`}`

旋转 2D 形状的挤出:
`rotate_extrude(angle=180) translate([10, 0]) circle(5);`

循环
重复形状:
`for (x=[0:10:40]) {`
`translate([x, 0, 0]) cube([5, 5, 10]);`
`}`

改变重复形状的特征:
`for (x=[0:1:4]) {`
`h = x*5 + 5;`
`translate([x*10, 0, 0]) cube([5, 5, h]);`
`}`

重复形状的重复:
`for (z=[0:15:45]) {`
`for (x=[0:10:40]) {`
`translate([x, 0, z]) cube([5, 5, 10]);`
`}`
`}`



浙公网安备 33010602011771号