第1章 边界表示 B-Rep

摘要:本章系统讲解边界表示(B-Rep)——工业界最主流的精确几何表示方法。首先阐述其核心思想:三维实体由边界曲面唯一确定,并强调几何与拓扑分离这一决定性特征。随后推导带孔实体上的欧拉-庞加莱公式 V−E+F−R=2(S−G)V - E + F - R = 2(S-G)VE+FR=2(SG),介绍欧拉算子、流形条件与非流形扩展、连续性语言(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 ≠ SurfaceSurface 是无界的数学曲面(例如 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)} VE+FR=2(SG)

我们先从上一章熟悉的球面情形出发,逐步推广。

步骤 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 VE+F=2

这正是我们上一章归纳证明过的四面体等式。

步骤 2:引入一个通孔(genus GGG 增加)。 设想在方块上钻一个贯穿孔。钻孔的过程在拓扑上等价于:取两个相对的面,各挖掉一个圆盘(各产生一个内环,故 RRR 增加 222),同时把这两个面“打通”使它们拓扑相连。一次穿孔使亏格 GGG 增加 1,而每打一个孔会在两端面各添一个内环(净增 RRR 关联)。把穿孔视为“在球面上挖一个把手(handle)”,每个把手使欧拉特征数减少 2,于是右侧从 222 变为 2(1−G)2(1-G)2(1G)

步骤 3:引入多个壳(shells SSS)。 若实体由 SSS 个互不相连的独立封闭壳组成(例如两个分开的方块),每一个壳各自贡献一个基础特征数 222,故右侧为 2S2S2S。综合步骤 2 与 3:

V−E+F−R=2 (S−G) V - E + F - R = 2\,(S - G) VE+FR=2(SG)

各符号含义与量纲(全为无量纲计数整数):

  • 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 起始,经 MEVMEF 逐步构造一个四面体,并在每步后打印 V−E+FV-E+FVE+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=2VE+F=46+4=2,与球面情形的欧拉公式一致。注意每一步 MVFS/MEV/MEF 都恰好保持 V−E+FV-E+FVE+F 不变——这正是欧拉算子的守恒性:它把“拓扑合法”编码进原子操作本身,任何合法序列的中间态都不会偏离 V−E+F=2V-E+F=2VE+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{ 的面构成单一循环扇} eE: {fF:ef}=2vV:  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),k1

工程现实: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^1C1G1反之不成立(可用重参数化把 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ΩxndA=31kDkSk(uSk×vSk)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_SolidTopoDS_ShellTopoDS_FaceTopoDS_Wire/TopoDS_EdgeTopoDS_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 为例,工业实现分五阶段:

  1. 包围盒过滤(BVH pre-filter):用 AABB 树把 O(n2)O(n^2)O(n2) 的面对相交检测降到近似 O(nlog⁡n)O(n\log n)O(nlogn),剔除绝不相交的面对。

  2. 面-面求交(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×n2n1×n2(切触处 n1∥n2\mathbf{n}_1\parallel\mathbf{n}_2n1n2 退化,是数值鲁棒性重灾区)。
  1. 交线拼接与拓扑分裂(Splitting):用容差统一(tolerance fusion) 把距离小于 ε\varepsilonε 的顶点合并,对应 OCCT 的 ShapeFix + 每子形状携带自身 tolerance。

  2. 分类(Classification):对每子面取内部一点 p\mathbf{p}p,射线投射或用绕数 wn⁡(p)\operatorname{wn}(\mathbf{p})wn(p) 判 IN / OUT / ON。

  3. 选面并缝合(Selection & Sewing)

运算 保留 A 的面 保留 B 的面 B 的面是否翻转
A∪∗BA \cup^{*} BAB OUT of B OUT of A
A∩∗BA \cap^{*} BAB IN of B IN of A
A−∗BA -^{*} BAB OUT of B IN of A

最后 Sewing 把选中面缝成封闭壳,并重算亏格与壳数校验欧拉公式

4.4 实现注意事项(血泪清单)

  1. 绝不用浮点相等判断拓扑。所有几何比较必须带容差,且容差要分层携带(顶点 tol > 边 tol > 面 tol),布尔后收紧/放松
  2. 精确谓词 (exact predicates):纯组合判断(点在平面哪侧、四点共球)用 Shewchuk 自适应精度算术,避免“符号翻转”致拓扑不一致。
  3. 参数域退化:球极点、圆锥顶点处 ∂S/∂u×∂S/∂v=0\partial\mathbf{S}/\partial u \times \partial\mathbf{S}/\partial v = \mathbf{0}S/u×S/v=0,法向未定义,依赖法向的算法都要有退化分支。
  4. 闭合面接缝 (seam edge):整圆柱面在 u=0u=0u=0u=2πu=2\piu=2π 处需一条 seam edge,它被同一面使用两次(两 coedge 属同一面),违反“一边两面”朴素假设。
  5. 共享子形状:OCCT 用 TShape + Location + Orientation 三元组做实例化共享,修改时须 Copy-on-Write,否则串改。

4.5 布尔运算失败排查清单

布尔运算(尤其 cut/fuse)是 B-Rep 内核里最容易“莫名失败”的环节。下面把工业界最常见的 5 类失败场景整理成排查清单,每类给出典型症状、根因与对应的 OCCT/FreeCAD 解决策略:

失败场景 典型症状 根因分析 OCCT/FreeCAD 解决策略
切触退化 (tangency) 两曲面相切处布尔失败或产生退化边/退化面;BOPAlgoBOPAlgo_TangentFaces 切触处 n1∥n2\mathbf{n}_1 \parallel \mathbf{n}_2n1n2,交线追踪方程 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×n2n1×n2 分母趋零,数值退化 ShapeFix_Shape 修复退化;对相切面做微小偏移(如 Part.offset 0.001)破坏切触;或改用 BOPAlgo_ArgumentAnalyzer 预检切触面
近奇异配置 (near-singular) 布尔结果出现细长条、微小碎面;结果看似合法但后续圆角/偏置失败 两曲面几乎平行或交角极小,交线参数化病态,容差内顶点合并产生"薄片" 提高输入精度(用 Part.makeBox 等解析构造而非网格导入);对结果执行 Part.refineShape() 合并共面碎面;必要时放大模型尺度再布尔
容差不足 (tolerance) 布尔后出现裂缝、重叠面;ShapeFixShapeExtend_Wire 未闭合;欧拉公式校验 V−E+F−R≠2(S−G)V-E+F-R \neq 2(S-G)VE+FR=2(SG) 输入形状容差过小,交线端点无法在容差内缝合;或 STEP 导入时容差被压缩 Part.Shape.tolerance 检查各子形状容差;调用 ShapeFix_ShapeTolerance::SetTolerance 统一/放大容差;布尔后 Part.refineShape() 收紧
非流形输入 (non-manifold) 布尔直接抛异常或返回空结果;BOPAlgoBOPAlgo_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 → VertexFace ≠ Surface,且每个“面-边”配对需一条有向 Coedge
    • 带孔/多壳的守恒律:V−E+F−R=2(S−G)V - E + F - R = 2(S-G)VE+FR=2(SG);改拓扑务必走欧拉算子
    • 连续性的语言是 GnG^nGn(几何)/ CnC^nCn(参数);A 级曲面要 G2G^2G2/G3G^3G3
    • 质量属性靠散度定理把体积分降为面积分,得到解析精度。
  • 适用场景

    • 机械 CAD、STEP 交换、CAM 刀路、CAE 前处理输入、工程图投影、MBD/PMI 语义挂载——凡需精确 + 可独立引用拓扑单元处,B-Rep 是首选。
  • 常见坑

    • 有效性维护极难:工业内核约 70% 代码在处理退化与容差,切勿低估。
    • 布尔/圆角在切触、近奇异配置下易失败——FreeCAD “布尔失败”多源于此。
    • 不易表达内部异质性(材料梯度、多孔晶格、拓扑优化密度场)→ 需体素/隐式。
    • 极高拓扑复杂度(数十万杆件晶格)下 B-Rep 面数爆炸,宜转向隐式/SDF。
    • 实现门槛高:从零写可用内核是十年量级工程,开源界实际只有 OCCT 一个选项。
  1. 环面(甜甜圈)的欧拉-庞加莱公式验证:用 FreeCAD 创建环面,先离散为三角网格,再统计网格的顶点、边、面数,代入公式验证 V−E+F−R=2(S−G)V-E+F-R=2(S-G)VE+FR=2(SG)
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) 把环面离散成三角网格,得到真正的顶点、边、三角面计数,才能代入公式验证。

  1. 环面验证的完整可运行代码:把上面的验证过程整理成一段可直接在 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) 返回三元组 (顶点列表, 边列表, 三角面列表),分别对应 VVVEEEFFF。环面是单壳(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=0VE+FR=02(S−G)=02(S-G)=02(SG)=0 严格相等——这也再次印证第 3.1 节的结论:欧拉公式适用于多面体剖分,解析曲面需先离散化才能计数。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐