本节摘要:SOURCE 2.1:几何元素从点线面到体,需在精度与效率间权衡;坐标系是空间认知的参照。
计算机无法直接"看见"形状,只能存储数字。设计者的每一次建模,本质上是把连续的几何对象离散成一系列数字结构。几何表示的层级可以这样理解:
| 层级 | 数学对象 | 表达内容 | 典型用途 |
|---|---|---|---|
| 点 | 坐标三元组 | 位置 | 顶点、控制点 |
| 线 | 参数方程 | 轨迹 | 边、曲线 |
| 面 | 参数方程/网格 | 表面 | 曲面片、三角面 |
| 体 | 拓扑 + 几何 | 封闭区域 | 实体模型 |
这一金字塔每一层都在回答"什么形状"和"它如何连接"两个问题。几何(Geometry) 回答前者——坐标与方程;拓扑(Topology) 回答后者——面由哪些边围成、边由哪些点界定。二者分离存储,是 CAD 数据结构的核心设计。
| 方法 | 存储内容 | 优势 | 局限 |
|---|---|---|---|
| 线框 | 边、顶点 | 轻量 | 无二义性体信息 |
| 曲面 | NURBS 片 | 自由外形 | 需封闭成体 |
| B-rep | 面-边-点拓扑 | 精确实体 | 算法复杂 |
| CSG | 布尔树 | 直观构造 | 难编辑局部 |
| 网格 | 三角/四边面 | 渲染快 | 近似、难参数化 |

选择表示方法的判据,是下游要拿模型做什么:做有限元分析,网格可以直接用;做数控加工,B-rep 才能可靠求交;做概念造型,NURBS 曲面最灵活;做标准件库,CSG 最直观。
SOURCE 强调 NURBS 用有理基函数统一圆锥与自由曲线。NURBS(非均匀有理 B 样条)之所以成为工业标准,是因为它用一个公式覆盖了几乎所有 CAD 需要的曲线类型:
NURBS 曲线的标准形式:
其中 P_i 是控制点,w_i 是权重,N_{i,p}(u) 是 p 次 B 样条基函数。权重 w_i 的存在,让 NURBS 比普通 B 样条多了一个"拉力"维度——权重越大,曲线越靠近该控制点,这使圆等精确圆锥曲线成为可能。
坐标系决定约束与装配的参考基准:
| 坐标系 | 定义 | 典型用途 |
|---|---|---|
| 世界坐标系 | 绝对参考,固定不动 | 装配、导出 |
| 局部坐标系 | 依附于零件/特征 | 草图、特征定义 |
| 视图坐标系 | 跟随观察视角 | 显示与拾取 |
工程中的大量 Bug 源于坐标系混用:在某零件的局部坐标系下输入了世界坐标数值、装配时把局部约束误当成全局约束、导出时忘记统一坐标系。实践中,建模规范通常会强制规定:新建零件一律以世界原点为第一特征基准。
几何表示的核心矛盾是精度与效率:B-rep 求交算法复杂度高但精确;网格渲染快但只是近似;NURBS 精确但求值计算比网格贵一个量级。CAD 系统普遍采用"双模型"策略:
# 双模型策略:设计模型(精确)与显示模型(离散)分离 class CadModel: def __init__(self, brep, tessellation): self.design = brep # B-rep,权威几何,用于求交/测量 self.display = tessellation # 三角网格,用于实时渲染 def render(self): # 渲染只用显示模型,保证帧率 for tri in self.display.triangles: rasterize(tri) def measure_distance(self, pt): # 测量/求交用设计模型,保证精度 return self.design.curve.distance_to(pt)
设计模型保证数学精度,显示模型保证交互流畅——两者在后台由"离散化/再离散化"过程同步,用户感知不到切换。这就是为什么你的 CAD 在旋转视图时流畅、但放大测量时依然精确。
网格(Mesh)是 CAD 生态里最容易被误用的表示。它的存储对象是三角形/四边形面片及顶点坐标,本质是对曲面的一种离散近似。下面用一个具体例子说明它的局限:
| 操作 | B-rep | 网格 |
|---|---|---|
| 测量两个面的夹角 | 从方程精确算出 | 只能按顶点法向近似 |
| 修改孔径 | 改参数,特征重算 | 无参数,只能拉顶点 |
| 圆角处光顺度 | 精确 G2 曲面 | 由面片密度决定 |
| 文件大小 | 紧凑(方程描述) | 随精度要求爆炸式增长 |
一个直径 10mm 的圆柱,B-rep 只需记录"两个圆 + 一个圆柱面"共三个几何实体;网格表示要达到 0.01mm 精度,可能需要上万个三角形。这就是为什么设计阶段用 B-rep/NURBS,交付与显示阶段才离散成网格。
# 三角网格数据结构的最小实现 class TriangleMesh: def __init__(self): self.vertices = [] # [(x, y, z), ...] self.faces = [] # [(v0, v1, v2, normal), ...] def add_box(self, size): # 示例:把盒子离散成 12 个三角形 s = size / 2 corners = [(-s, -s, -s), (s, -s, -s), (s, s, -s), (-s, s, -s), (-s, -s, s), (s, -s, s), (s, s, s), (-s, s, s)] quads = [(0,1,2,3), (4,5,6,7), (0,1,5,4), (2,3,7,6), (0,3,7,4), (1,2,6,5)] for a, b, c, d in quads: self.faces.append((a, b, c, normal_of(corners, a, b, c))) self.faces.append((a, c, d, normal_of(corners, a, c, d))) def export_stl(self): # STL 文件就是"三角形列表 + 法向",因此无法携带拓扑语义 lines = ["solid mesh"] for (a, b, c, n) in self.faces: lines.append(f" facet normal {n}") lines.append(f" outer loop") for vi in (a, b, c): lines.append(f" vertex {self.vertices[vi]}") lines.append(" endloop") lines.append(" endfacet") lines.append("endsolid mesh") return "\n".join(lines)
看完这段代码,你就明白为什么 STL 无法参数化修改:它只存了三角形,没有存"这个三角形属于圆柱面还是平面"。
CAD 的所有计算都基于浮点数,而浮点数无法精确表示 0.1 这类十进制小数。两个"数学上应该重合"的点,在计算机里可能差 1e-9。因此每个 CAD 内核都引入**容差(Tolerance)**概念:
容差设计是内核稳健性的核心命题:
| 容差过小 | 容差过大 |
|---|---|
| 本应重合的点被当成分离 → 布尔失败 | 本应分离的面被焊在一起 → 产生坏几何 |
| 微小碎面无法清理 | 小特征被吞并 |
| 装配"看起来没对齐" | 干涉检查漏报 |
这也是为什么从其他软件导入的模型常常"看着不干净"——两边内核的容差策略不同,几何判定结果就不同。理解容差,是理解"为什么有时明明相交却求交失败"的钥匙。
实际设计流程中,同一条数据链会穿越多种表示:
每次转换都是一次"翻译",而翻译必然有损耗:NURBS→B-rep 可能退化曲面阶数,B-rep→网格丢失拓扑,STEP 交换丢失特征。工程师对表示转换的敏感性,决定了他能否在设计链路的每个节点做出正确选择。
⚠️ 常见坑:STEP 转换丢特征——中性格式常只保留 B-rep 快照。
💡 关键直觉:没有「最好」表示,只有「当前任务最合适」表示。