几何表示法研究(三)
第1章 边界表示 B-Rep
摘要:本章系统讲解边界表示(B-Rep)——工业界最主流的精确几何表示方法。首先阐述其核心思想:三维实体由边界曲面唯一确定,并强调几何与拓扑分离这一决定性特征。随后推导带孔实体上的欧拉-庞加莱公式 V−E+F−R=2(S−G)V - E + F - R = 2(S-G)V−E+F−R=2(S−G),介绍欧拉算子、流形条件与非流形扩展、连续性语言(GnG^nGn/CnC^nCn)以及基于散度定理的解析积分优势。在实现层面,对比翼边、半边、四边、径向边等数据结构,并以 FreeCAD/OCCT 为例演示布尔运算流水线与欧拉公式验证。最后通过带孔方块的完整示例,展示 B-Rep 在机械 CAD 中的实际应用与常见陷阱。
一、背景与动机
上一章我们建立了一把标尺(六准则)和一张地图(分类维度)。现在走进第一种、也是工业界最重量级的精确·边界表示:边界表示(Boundary Representation,B-Rep)。
为什么先从它讲起?因为主流 CAD 内核——Parasolid、ACIS、CGM、OCCT(FreeCAD/KiCad/Salome 都基于它)——本质都是 B-Rep 内核。你想用 FreeCAD 建模、用 STEP 交换、把模型喂给 Gmsh 划分网格,背后全是 B-Rep 在支撑。
那它解决什么真实痛点?想象你要描述一个机械轴承座:外有圆角、内有台阶孔、侧面要攻螺纹。如果像体素那样“逐格记录占据”,数据量会爆炸;如果像纯线框那样“只存棱线”,又会有多解、不完备(同一组棱线能围出不同实体)。B-Rep 的洞见非常漂亮:
核心思想:一个三维实体由其边界曲面唯一确定。所以只要完整、无歧义地描述边界,并规定“哪一侧是材料”,实体就完全确定了。
本章带你从拓扑/几何分离讲起,推导它在带孔实体上的欧拉-庞加莱推广,并落到 FreeCAD/OCCT 的具体实现与避坑清单。相关公理框架参见 00-导论-几何表示总览.md。
二、核心概念与原理
2.1 几何与拓扑分离
B-Rep 的决定性特征是几何(geometry)与拓扑(topology)分离:
- 拓扑回答“谁和谁相邻”:顶点、边、环、面、壳、体之间的连接关系,是纯组合结构;
- 几何回答“具体长什么样”:点坐标、曲线方程、曲面方程,是连续数学对象。
打个比方:拓扑像一份地铁线路图(站与线的连接关系),几何像把这张图按真实地理坐标铺到地图上。改坐标(几何)不影响连通关系(拓扑),这正是 B-Rep 能独立编辑形状、又能稳定引用“某条边”的根本原因。
2.2 拓扑层次
标准拓扑层次(自顶向下,OCCT/Parasolid/ACIS 高度一致):
Body / Solid(体)
└── Shell(壳,封闭定向曲面集合)
└── Face(面 = 曲面的有界裁剪片)
├── Surface(几何载体:平面/圆柱/NURBS…)
└── Loop/Wire(环,一个外环 + N 个内环)
└── Coedge/Half-edge(有向边使用)
└── Edge(边)
├── Curve(几何载体 + 参数区间)
└── Vertex(顶点 → Point)
三个关键澄清(初学时极易混淆):
- Face ≠ Surface。
Surface是无界的数学曲面(例如 z=0z=0z=0 的整个平面);Face是它被一组环裁剪出的有界区域,还携带“法向朝外 / 朝内”的方位标志(OCCT 中即TopAbs_Orientation)。 - Coedge/Half-edge 的必要性:一条边被两个面共享,但两个面对它的走向要求相反(各自环的绕行方向需与面法向满足右手规则)。所以必须为每个“面-边”配对建一条有向记录。
- Trimming 的双重表达:面上的环既有三维曲线(3D edge curve),又有参数域内的二维曲线(p-curve)。两者必须一致——容差不匹配是工业界最常见的数据交换故障源。
三、关键公式与推导
3.1 欧拉-庞加莱公式在带孔实体上的推广
B-Rep 的有效性检验依赖代数拓扑的欧拉-庞加莱特征数。对一个由多面体单元剖分的定向闭曲面集合:
V−E+F−R=2 (S−G) \boxed{V - E + F - R = 2\,(S - G)} V−E+F−R=2(S−G)
我们先从上一章熟悉的球面情形出发,逐步推广。
步骤 1:球面简单多面体。 当只有一个封闭壳、无孔、无内环时,S=1, G=0, R=0S=1,\ G=0,\ R=0S=1, G=0, R=0,公式退化为
V−E+F=2 V - E + F = 2 V−E+F=2
这正是我们上一章归纳证明过的四面体等式。
步骤 2:引入一个通孔(genus GGG 增加)。 设想在方块上钻一个贯穿孔。钻孔的过程在拓扑上等价于:取两个相对的面,各挖掉一个圆盘(各产生一个内环,故 RRR 增加 222),同时把这两个面“打通”使它们拓扑相连。一次穿孔使亏格 GGG 增加 1,而每打一个孔会在两端面各添一个内环(净增 RRR 关联)。把穿孔视为“在球面上挖一个把手(handle)”,每个把手使欧拉特征数减少 2,于是右侧从 222 变为 2(1−G)2(1-G)2(1−G)。
步骤 3:引入多个壳(shells SSS)。 若实体由 SSS 个互不相连的独立封闭壳组成(例如两个分开的方块),每一个壳各自贡献一个基础特征数 222,故右侧为 2S2S2S。综合步骤 2 与 3:
V−E+F−R=2 (S−G) V - E + F - R = 2\,(S - G) V−E+F−R=2(S−G)
各符号含义与量纲(全为无量纲计数整数):
- VVV:顶点 (vertex) 数;
- EEE:边 (edge) 数;
- FFF:面 (face) 数;
- RRR:内环 (inner ring) 总数(孔环,一个通孔在两个端面各贡献一个);
- SSS:壳 (shell) 数;
- GGG:亏格 (genus),即穿孔数(一个贯穿孔 G=1G=1G=1)。
守恒律意义:这条公式是 B-Rep 数据结构变更的“能量守恒”。Baumgart / Braid 提出的欧拉算子 (Euler operators) 系统性给出保持该等式的原子操作:
| 算子 | 语义 | 对 (V,E,F,R,S,G)(V,E,F,R,S,G)(V,E,F,R,S,G) 的增量 |
|---|---|---|
MEV |
Make Edge & Vertex | +1,+1,0,0,0,0+1,+1,0,0,0,0+1,+1,0,0,0,0 |
MEF |
Make Edge & Face | 0,+1,+1,0,0,00,+1,+1,0,0,00,+1,+1,0,0,0 |
KEMR |
Kill Edge, Make Ring | 0,−1,0,+1,0,00,-1,0,+1,0,00,−1,0,+1,0,0 |
MEKR |
Make Edge, Kill Ring | 0,+1,0,−1,0,00,+1,0,-1,0,00,+1,0,−1,0,0 |
KFMRH |
Kill Face, Make Ring & Hole | 0,0,−1,+1,0,+10,0,-1,+1,0,+10,0,−1,+1,0,+1 |
MVFS |
Make Vertex, Face, Shell | +1,0,+1,0,+1,0+1,0,+1,0,+1,0+1,0,+1,0,+1,0 |
任何合法 B-Rep 都可由 MVFS 起始、经有限次欧拉算子构造。内核开发的最佳实践,就是把所有拓扑修改封装为欧拉算子,从而在构造层面杜绝拓扑非法。
下面用 Python 风格伪代码演示:从 MVFS 起始,经 MEV 与 MEF 逐步构造一个四面体,并在每步后打印 V−E+FV-E+FV−E+F 验证始终等于 222:
# 欧拉算子构造四面体:MVFS 起始 → 3×MEV → 3×MEF
V = E = F = S = G = R = 0
def mvfs():
"""Make Vertex, Face, Shell:创建孤立顶点 + 一个空壳面"""
global V, F, S
V += 1; F += 1; S += 1
report("MVFS")
def mev(v_from, v_to):
"""Make Edge & Vertex:从已有顶点连一条边,并新建一个顶点"""
global V, E
E += 1; V += 1
report("MEV")
def mef(v1, v2):
"""Make Edge & Face:在已有两顶点间加一条边,并闭合一个新面"""
global E, F
E += 1; F += 1
report("MEF")
def report(op):
print(f"{op:5s} → V={V}, E={E}, F={F}, V-E+F = {V-E+F}")
# 1) MVFS:孤立顶点 v0 + 空壳面
mvfs() # V=1, E=0, F=1 → V-E+F = 2
# 2) 三次 MEV:从 v0 长出 v1, v2, v3,形成三条辐射边
mev(0, 1) # V=2, E=1, F=1 → V-E+F = 2
mev(0, 2) # V=3, E=2, F=1 → V-E+F = 2
mev(0, 3) # V=4, E=3, F=1 → V-E+F = 2
# 3) 三次 MEF:依次闭合底面、侧面、侧面,封出 4 个三角面
mef(1, 2) # V=4, E=4, F=2 → V-E+F = 2
mef(2, 3) # V=4, E=5, F=3 → V-E+F = 2
mef(3, 1) # V=4, E=6, F=4 → V-E+F = 2 ✓ 四面体完成
走查:最终 V=4, E=6, F=4V=4,\ E=6,\ F=4V=4, E=6, F=4,代入 V−E+F=4−6+4=2V-E+F=4-6+4=2V−E+F=4−6+4=2,与球面情形的欧拉公式一致。注意每一步 MVFS/MEV/MEF 都恰好保持 V−E+FV-E+FV−E+F 不变——这正是欧拉算子的守恒性:它把“拓扑合法”编码进原子操作本身,任何合法序列的中间态都不会偏离 V−E+F=2V-E+F=2V−E+F=2。
3.2 流形条件与非流形扩展
二维流形 (2-manifold) 条件:∂Ω\partial\Omega∂Ω 上每点的邻域都同胚于 R2\mathbb{R}^2R2 的开圆盘。组合层面等价于:
∀e∈E: ∣{f∈F:e⊂∂f}∣=2且∀v∈V: 绕 v 的面构成单一循环扇 \forall e \in E:\ |\{f \in F : e \subset \partial f\}| = 2 \quad\text{且}\quad \forall v \in V:\ \text{绕 } v \text{ 的面构成单一循环扇} ∀e∈E: ∣{f∈F:e⊂∂f}∣=2且∀v∈V: 绕 v 的面构成单一循环扇
违反的典型情形:一条边被 3 个面共享(T 型接头)、两个立方体仅顶点相接(蝴蝶结点)。
为什么需要扩展? 工程里常需混合维度(实体 + 曲面 + 线框共存,如钣金中面模型、装配体接触面)。此时一条边被任意多个面共享,上述“每边恰两面”的流形假设崩塌。Weiler 的径向边结构 (Radial-Edge Structure, RES) 提出:在 Edge 周围维护一个按角度排序的 face-use 环形链表,允许任意多个面共享一条边:
Edge→{Edge-Use1,…,Edge-Usek} (radially ordered),k≥1 \text{Edge} \to \{\text{Edge-Use}_1, \dots, \text{Edge-Use}_k\}\ \text{(radially ordered)},\quad k \geq 1 Edge→{Edge-Use1,…,Edge-Usek} (radially ordered),k≥1
工程现实:OCCT 采用允许非流形的宽松拓扑模型——
TopoDS_Shape是任意有向无环图 (DAG),因此能承载非流形;但布尔与圆角算法对非流形输入的鲁棒性会明显下降。
3.3 微分几何:连续性的语言
面与面沿共享边的连续性用 GnG^nGn(几何连续)与 CnC^nCn(参数连续)刻画:
G0:位置连续(点重合)G1:切平面连续 ⟺ n1=n2 沿边处处成立G2:曲率连续 ⟺ 两侧第二基本形式在切向上一致 \begin{aligned} G^0 &: \text{位置连续(点重合)}\\ G^1 &: \text{切平面连续}\ \Longleftrightarrow\ \mathbf{n}_1 = \mathbf{n}_2 \text{ 沿边处处成立}\\ G^2 &: \text{曲率连续}\ \Longleftrightarrow\ \text{两侧第二基本形式在切向上一致} \end{aligned} G0G1G2:位置连续(点重合):切平面连续 ⟺ n1=n2 沿边处处成立:曲率连续 ⟺ 两侧第二基本形式在切向上一致
注意 C1⇒G1C^1 \Rightarrow G^1C1⇒G1 但反之不成立(可用重参数化把 G1G^1G1 提升)。汽车外覆盖件等 A 级曲面 (Class-A surface) 要求 G2G^2G2 甚至 G3G^3G3,因为高光反射线对曲率不连续极其敏感。
3.4 质量属性的解析积分(杀手级优势)
B-Rep 最被低估的优势:用散度定理(Gauss 定理) 把体积分降为面积分。体积公式为
Vol(Ω)=∭ΩdV=Gauss13∮∂Ωx⋅n dA=13∑k∬DkSk⋅(∂Sk∂u×∂Sk∂v)du dv \operatorname{Vol}(\Omega) = \iiint_\Omega dV \overset{\text{Gauss}}{=} \frac{1}{3}\oint_{\partial\Omega} \mathbf{x}\cdot \mathbf{n}\, dA = \frac{1}{3}\sum_{k} \iint_{D_k} \mathbf{S}_k \cdot \left( \frac{\partial \mathbf{S}_k}{\partial u} \times \frac{\partial \mathbf{S}_k}{\partial v} \right) du\,dv Vol(Ω)=∭ΩdV=Gauss31∮∂Ωx⋅ndA=31k∑∬DkSk⋅(∂u∂Sk×∂v∂Sk)dudv
Sk\mathbf{S}_kSk 为第 kkk 个面的参数曲面,DkD_kDk 为其参数域。重心与惯量张量同理(换不同被积函数即可)。这正是 CAD 能给解析精度质量特性,而网格只能给近似值的原因。
四、具体实现步骤
4.1 数据结构选型
| 结构 | 提出者/年代 | 支持流形? | 特点 |
|---|---|---|---|
| 翼边 Winged-Edge | Baumgart 1972 | 仅流形 | 边为中心,遍历需判方向,易出错 |
| 半边 Half-Edge (DCEL) | Mäntylä 1988 | 仅流形 | 有向边为中心,遍历代码最简洁 |
| 四边 Quad-Edge | Guibas-Stolfi 1985 | 流形+对偶 | 同时表示图与其对偶,Delaunay 首选 |
| 径向边 Radial-Edge | Weiler 1986 | 非流形 | 工业内核(ACIS/Parasolid)基础 |
| 有向无环图 (OCCT) | OCCT | 非流形 | TopoDS_TShape + 位置/方位分离,支持共享子形状 |
半边遍历核心不变式(实现时务必逐条断言):
he.twin.twin == he
he.twin.origin == he.next.origin
he.next.prev == he
he.face == he.next.face
遍历面: he → he.next → … 回到起点,且 he.face 恒定
遍历点邻域(1-ring): he → he.twin.next → … 回到起点
4.2 OCCT 拓扑层级(落地到 FreeCAD)
在 OCCT 里,上一节的层次变成具体类:TopoDS_Solid → TopoDS_Shell → TopoDS_Face → TopoDS_Wire/TopoDS_Edge → TopoDS_Vertex。可用 Python(FreeCAD 的 Part 模块)遍历:
import Part
shape = Part.makeBox(10, 10, 10)
# 统计拓扑单元(演练欧拉公式)
print("Vertex:", len(shape.Vertexes)) # V
print("Edge: ", len(shape.Edges)) # E
print("Face: ", len(shape.Faces)) # F
# 对无孔方块:V=8, E=12, F=6 → V-E+F = 2 ✓
4.3 布尔运算流水线(B-Rep 最核心也最难的算法)
以 OCCT 的 BOPAlgo 为例,工业实现分五阶段:
-
包围盒过滤(BVH pre-filter):用 AABB 树把 O(n2)O(n^2)O(n2) 的面对相交检测降到近似 O(nlogn)O(n\log n)O(nlogn),剔除绝不相交的面对。
-
面-面求交(SSI):求解 S1(u1,v1)=S2(u2,v2)\mathbf{S}_1(u_1,v_1) = \mathbf{S}_2(u_2,v_2)S1(u1,v1)=S2(u2,v2)(3 方程 4 未知,解集为 1 维曲线)。分三类处理:
- 解析-解析:平面/平面→直线;圆柱/平面→圆/椭圆/双直线(走专门分支保精度);
- 参数-隐式:把 S2\mathbf{S}_2S2 隐式化为 g(x)=0g(\mathbf{x})=0g(x)=0,代入得 g(S1(u,v))=0g(\mathbf{S}_1(u,v))=0g(S1(u,v))=0 再做追踪法;
- 通用 NURBS/NURBS:细分包围定起点,微分方程追踪:dxds=n1×n2∥n1×n2∥\frac{d\mathbf{x}}{ds} = \frac{\mathbf{n}_1 \times \mathbf{n}_2}{\|\mathbf{n}_1 \times \mathbf{n}_2\|}dsdx=∥n1×n2∥n1×n2(切触处 n1∥n2\mathbf{n}_1\parallel\mathbf{n}_2n1∥n2 退化,是数值鲁棒性重灾区)。
-
交线拼接与拓扑分裂(Splitting):用容差统一(tolerance fusion) 把距离小于 ε\varepsilonε 的顶点合并,对应 OCCT 的
ShapeFix+ 每子形状携带自身 tolerance。 -
分类(Classification):对每子面取内部一点 p\mathbf{p}p,射线投射或用绕数 wn(p)\operatorname{wn}(\mathbf{p})wn(p) 判 IN / OUT / ON。
-
选面并缝合(Selection & Sewing):
| 运算 | 保留 A 的面 | 保留 B 的面 | B 的面是否翻转 |
|---|---|---|---|
| A∪∗BA \cup^{*} BA∪∗B | OUT of B | OUT of A | 否 |
| A∩∗BA \cap^{*} BA∩∗B | IN of B | IN of A | 否 |
| A−∗BA -^{*} BA−∗B | OUT of B | IN of A | 是 |
最后 Sewing 把选中面缝成封闭壳,并重算亏格与壳数校验欧拉公式。
4.4 实现注意事项(血泪清单)
- 绝不用浮点相等判断拓扑。所有几何比较必须带容差,且容差要分层携带(顶点 tol > 边 tol > 面 tol),布尔后收紧/放松。
- 精确谓词 (exact predicates):纯组合判断(点在平面哪侧、四点共球)用 Shewchuk 自适应精度算术,避免“符号翻转”致拓扑不一致。
- 参数域退化:球极点、圆锥顶点处 ∂S/∂u×∂S/∂v=0\partial\mathbf{S}/\partial u \times \partial\mathbf{S}/\partial v = \mathbf{0}∂S/∂u×∂S/∂v=0,法向未定义,依赖法向的算法都要有退化分支。
- 闭合面接缝 (seam edge):整圆柱面在 u=0u=0u=0 与 u=2πu=2\piu=2π 处需一条 seam edge,它被同一面使用两次(两 coedge 属同一面),违反“一边两面”朴素假设。
- 共享子形状:OCCT 用
TShape + Location + Orientation三元组做实例化共享,修改时须Copy-on-Write,否则串改。
4.5 布尔运算失败排查清单
布尔运算(尤其 cut/fuse)是 B-Rep 内核里最容易“莫名失败”的环节。下面把工业界最常见的 5 类失败场景整理成排查清单,每类给出典型症状、根因与对应的 OCCT/FreeCAD 解决策略:
| 失败场景 | 典型症状 | 根因分析 | OCCT/FreeCAD 解决策略 |
|---|---|---|---|
| 切触退化 (tangency) | 两曲面相切处布尔失败或产生退化边/退化面;BOPAlgo 报 BOPAlgo_TangentFaces |
切触处 n1∥n2\mathbf{n}_1 \parallel \mathbf{n}_2n1∥n2,交线追踪方程 dxds=n1×n2∣n1×n2∣\frac{d\mathbf{x}}{ds} = \frac{\mathbf{n}_1 \times \mathbf{n}_2}{|\mathbf{n}_1 \times \mathbf{n}_2|}dsdx=∣n1×n2∣n1×n2 分母趋零,数值退化 | 用 ShapeFix_Shape 修复退化;对相切面做微小偏移(如 Part.offset 0.001)破坏切触;或改用 BOPAlgo_ArgumentAnalyzer 预检切触面 |
| 近奇异配置 (near-singular) | 布尔结果出现细长条、微小碎面;结果看似合法但后续圆角/偏置失败 | 两曲面几乎平行或交角极小,交线参数化病态,容差内顶点合并产生"薄片" | 提高输入精度(用 Part.makeBox 等解析构造而非网格导入);对结果执行 Part.refineShape() 合并共面碎面;必要时放大模型尺度再布尔 |
| 容差不足 (tolerance) | 布尔后出现裂缝、重叠面;ShapeFix 报 ShapeExtend_Wire 未闭合;欧拉公式校验 V−E+F−R≠2(S−G)V-E+F-R \neq 2(S-G)V−E+F−R=2(S−G) |
输入形状容差过小,交线端点无法在容差内缝合;或 STEP 导入时容差被压缩 | 用 Part.Shape.tolerance 检查各子形状容差;调用 ShapeFix_ShapeTolerance::SetTolerance 统一/放大容差;布尔后 Part.refineShape() 收紧 |
| 非流形输入 (non-manifold) | 布尔直接抛异常或返回空结果;BOPAlgo 报 BOPAlgo_NonManifold |
输入含 T 型接头、蝴蝶结点等非流形边/点(见 3.2 节),布尔分类阶段无法确定 IN/OUT | 先用 Part.check() 检测非流形;用 ShapeFix_Shape 修复;或把非流形拆成多个流形实体分别布尔再 Part.fuse 合并 |
| 参数域退化 (parametric degeneracy) | 球极点、圆锥顶点附近布尔失败;依赖法向的算法(如偏置)在退化点崩溃 | ∂S/∂u×∂S/∂v=0\partial\mathbf{S}/\partial u \times \partial\mathbf{S}/\partial v = \mathbf{0}∂S/∂u×∂S/∂v=0,法向未定义,分类/缝合在该处失效 | 避免让退化点落在交线上;用 ShapeFix 在退化点处切分面;偏置时对退化点做特殊分支处理(见 4.4 第 3 条) |
排查口诀:先 Part.check() 验输入合法性 → 再 ShapeFix 修容差与非流形 → 仍失败则怀疑切触/近奇异,用微小偏移或分解布尔序列(把一次复杂布尔拆成多次简单布尔,逐步定位失败面)逐个击破。
五、实际应用示例
目标:用 FreeCAD / OCCT 给"带孔方块"做布尔挖孔,并验证欧拉公式。
输入:一个 10×10×1010\times10\times1010×10×10 方块 + 一个半径 222、高 121212 的圆柱(沿 Z 轴贯穿)。
过程(FreeCAD Python 控制台):
import Part
box = Part.makeBox(10, 10, 10) # 方块
cyl = Part.makeCylinder(2, 12) # 圆柱
# 布尔差:在方块上挖掉圆柱(正则化布尔,结果必为合法 r-set)
result = box.cut(cyl) # 对应 A -* B
print("V,E,F =", len(result.Vertexes),
len(result.Edges), len(result.Faces))
# 走查欧拉公式:单壳(S=1)、单通孔(G=1)
# 通孔在两个端面各产生一个内环 → R = 2
# 应有 V - E + F - R = 2*(1-1) = 0
输出:result 是一个解析精确的带孔实体——圆柱孔的直径永远精确为 444,不依赖任何离散化。若改用网格做同样挖孔,几乎必然在孔壁产生非流形边。我们可以进一步求值质量属性:
print("Volume =", result.Volume) # 解析积分,工程级精度
这正体现了第 3.4 节散度定理的威力:体积由边界曲面直接解析积分得到,无需体素化。最后把结果导出为 result.exportStep("holey_box.step"),即标准化的 B-Rep 交换格式。
五·五、B-Rep 与网格表示对比
B-Rep 与体素/网格是两条截然不同的技术路线:前者追求解析精确,后者追求离散逼近。下表从五个关键维度做对比:
| 维度 | B-Rep | 体素 / 网格 |
|---|---|---|
| 精度 | 解析精确:圆柱孔直径永远精确为 444,不依赖离散化 | 离散逼近:精度受网格密度/体素分辨率限制,加密才收敛 |
| 数据量 | 紧凑:只存边界曲面与拓扑,曲面用方程描述 | 庞大:逐格/逐单元记录,复杂曲面需海量单元逼近 |
| 布尔运算 | 精确但昂贵:需面-面求交、容差统一、分类缝合,切触处易失败 | 简单快速:逐单元标记 IN/OUT,但结果带锯齿、易产生非流形边 |
| 质量属性计算 | 解析积分:散度定理把体积分降为面积分,工程级精度 | 数值近似:体积/重心靠单元求和,精度依赖网格质量 |
| 适用场景 | 机械 CAD、STEP 交换、CAM 刀路、工程图投影——凡需精确与可独立引用拓扑单元处 | 有限元/CFD 求解、拓扑优化、体素化渲染、3D 打印切片——凡需离散求解或表达内部异质性处 |
典型应用场景说明:
- B-Rep:机械零件设计——一个带台阶孔和圆角的轴承座,用 B-Rep 精确建模并导出 STEP,供下游 CAM 生成刀路、CAE 划分网格,全程保持几何无歧义。
- 体素/网格:有限元分析——把 B-Rep 模型离散成四面体网格后交给求解器,用单元积分近似应力应变;或拓扑优化中直接用体素密度场表达材料分布。
六、小结与要点
-
记住什么
- B-Rep = “边界曲面 + 材料侧规定”,核心是几何/拓扑分离:拓扑管邻接,几何管形状。
- 拓扑层级:
Solid → Shell → Face → Wire → Edge → Vertex;Face ≠ Surface,且每个“面-边”配对需一条有向Coedge。 - 带孔/多壳的守恒律:V−E+F−R=2(S−G)V - E + F - R = 2(S-G)V−E+F−R=2(S−G);改拓扑务必走欧拉算子。
- 连续性的语言是 GnG^nGn(几何)/ CnC^nCn(参数);A 级曲面要 G2G^2G2/G3G^3G3。
- 质量属性靠散度定理把体积分降为面积分,得到解析精度。
-
适用场景
- 机械 CAD、STEP 交换、CAM 刀路、CAE 前处理输入、工程图投影、MBD/PMI 语义挂载——凡需精确 + 可独立引用拓扑单元处,B-Rep 是首选。
-
常见坑
- 有效性维护极难:工业内核约 70% 代码在处理退化与容差,切勿低估。
- 布尔/圆角在切触、近奇异配置下易失败——FreeCAD “布尔失败”多源于此。
- 不易表达内部异质性(材料梯度、多孔晶格、拓扑优化密度场)→ 需体素/隐式。
- 极高拓扑复杂度(数十万杆件晶格)下 B-Rep 面数爆炸,宜转向隐式/SDF。
- 实现门槛高:从零写可用内核是十年量级工程,开源界实际只有 OCCT 一个选项。
- 环面(甜甜圈)的欧拉-庞加莱公式验证:用 FreeCAD 创建环面,先离散为三角网格,再统计网格的顶点、边、面数,代入公式验证 V−E+F−R=2(S−G)V-E+F-R=2(S-G)V−E+F−R=2(S−G):
import Part torus = Part.makeTorus(10, 2) # 主半径 10,管半径 2 # 环面是解析曲面,B-Rep 表示中 V=0, E=0, F=1,无法直接套用多面体公式 # 先用 tessellate(0.5) 离散为三角网格(0.5 为弦高容差,越小越密) mesh = torus.tessellate(0.5) V = len(mesh[0]) # 网格顶点数 E = len(mesh[1]) # 网格边数 F = len(mesh[2]) # 网格三角面数 print("V,E,F =", V, E, F) # 环面是单壳(S=1)、单通孔(G=1)、无内环(R=0) # 代入公式:V - E + F - R = 2*(S - G) = 2*(1-1) = 0 print("V-E+F-R =", V - E + F - 0) # 应等于 0 ✓取值依据:环面是单壳实体(S=1S=1S=1),有一个贯穿孔(G=1G=1G=1),且环面没有内环(R=0R=0R=0)。但 FreeCAD 的
Part.makeTorus返回的是解析曲面,其 B-Rep 表示中 V=0, E=0, F=1V=0,\ E=0,\ F=1V=0, E=0, F=1——它没有离散成多面体,因此不能直接套用多面体版本的欧拉公式。这正是矛盾(1≠01\neq 01=0)的根源:欧拉公式的 V,E,FV,E,FV,E,F 是对多面体剖分的计数,而解析曲面只有一张无界裁剪面,没有顶点、边、多面体面可言。必须先调用tessellate(0.5)把环面离散成三角网格,得到真正的顶点、边、三角面计数,才能代入公式验证。
- 环面验证的完整可运行代码:把上面的验证过程整理成一段可直接在 FreeCAD Python 控制台运行的完整脚本,显式打印 S=1, G=1, R=0S=1,\ G=1,\ R=0S=1, G=1, R=0 的取值依据:
import Part # 1) 创建环面:主半径 10,管半径 2 torus = Part.makeTorus(10, 2) # 2) 离散为三角网格:0.5 为弦高容差,越小网格越密 mesh = torus.tessellate(0.5) # 3) 统计网格的顶点、边、三角面数 V = len(mesh[0]) # 网格顶点数 E = len(mesh[1]) # 网格边数 F = len(mesh[2]) # 网格三角面数 print("V, E, F =", V, E, F) # 4) 代入公式 V - E + F - R = 2(S - G) 验证 # 取值依据: # S = 1:环面是单个封闭壳(甜甜圈表面是一整张闭曲面) # G = 1:环面有一个贯穿孔(甜甜圈的洞),亏格为 1 # R = 0:环面没有内环(没有额外的孔环) S, G, R = 1, 1, 0 lhs = V - E + F - R # 公式左侧 rhs = 2 * (S - G) # 公式右侧 print("S =", S, " G =", G, " R =", R) print("V - E + F - R =", lhs) print("2*(S - G) =", rhs) print("验证结果:", "通过 ✓" if lhs == rhs else "不通过 ✗")运行说明:
tessellate(0.5)返回三元组(顶点列表, 边列表, 三角面列表),分别对应 VVV、EEE、FFF。环面是单壳(S=1S=1S=1)、单通孔(G=1G=1G=1)、无内环(R=0R=0R=0),代入后左右两侧都等于 000,等式成立。若直接对解析曲面torus统计,会得到 V=0, E=0, F=1V=0,\ E=0,\ F=1V=0, E=0, F=1,代入得 1≠01\neq 01=0——这正是必须先离散化的原因。
七、参考资料
- Baumgart, B. G. (1972). Winged Edge Polyhedron Representation. Stanford Artificial Intelligence Project, Stanford University. 翼边结构(Winged-Edge)的奠基性技术报告,首次提出以边为中心的多面体拓扑表示。
- Mäntylä, M. (1988). An Introduction to Solid Modeling. Computer Science Press. 系统阐述半边结构(Half-Edge / DCEL)与欧拉算子构造实体的经典著作,是 B-Rep 数据结构的标准教材。
- Weiler, K. (1986). Topological Structures for Geometric Modeling. Ph.D. Thesis, Rensselaer Polytechnic Institute. 提出径向边结构(Radial-Edge Structure, RES),支持非流形拓扑,是 ACIS/Parasolid 等工业内核的理论基础。
- Guibas, L. & Stolfi, J. (1985). Primitives for the Manipulation of General Subdivisions and the Computation of Voronoi Diagrams. ACM Transactions on Graphics, 4(2), 74–123. 提出四边结构(Quad-Edge),同时表示图与其对偶,是 Delaunay 三角化与计算几何的经典论文。
- Open CASCADE Technology (OCCT). Topology and Geometry in OCCT — TopoDS_Shape, TopoDS_TShape, Location & Orientation. Open Cascade SAS 官方文档。 说明
TopoDS_Shape的 DAG 结构、共享子形状与位置/方位分离机制。 - FreeCAD Documentation. Part Module — Part.makeTorus, Part.makeBox, tessellate(), cut(), refineShape(). FreeCAD Wiki。 FreeCAD
Part模块的 Python API 参考,覆盖布尔运算、离散化与拓扑遍历。 - O’Rourke, J. (1998). Computational Geometry in C (2nd ed.). Cambridge University Press. 计算几何标准教材,其中对欧拉公式、多面体拓扑与平面剖分的组合计数有严谨推导。
- Hoffmann, C. M. (1989). Geometric and Solid Modeling: An Introduction. Morgan Kaufmann. 系统讲解 B-Rep、CSG 与欧拉-庞加莱公式在实体建模中应用的权威专著。
离散后网格是闭流形,V−E+F−R=0V-E+F-R=0V−E+F−R=0 与 2(S−G)=02(S-G)=02(S−G)=0 严格相等——这也再次印证第 3.1 节的结论:欧拉公式适用于多面体剖分,解析曲面需先离散化才能计数。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)