Technical note

为什么 CAD/CAE 软件需要一个自己的几何智能层

从几何内核直接驱动业务时遇到的问题出发,讨论为什么 CAD/CAE 软件在 B-Rep 之上还需要一层长期稳定的拓扑、接口、区域与特征表达,以及它如何连接几何修复、共形网格和后续仿真流程。

背景:直接使用 B-Rep,在项目早期其实很自然

大多数 CAD 功能刚开始做时,最直接的方式就是操作几何内核里的 Shape。

例如:

STEP
    ↓
B-Rep Shape
    ↓
遍历 Solid / Face / Edge
    ↓
业务算法

对于建模、导入导出、简单修复,这种方式非常有效。

几何内核已经提供了成熟的:

拓扑结构;
曲线曲面;
Boolean;
Sewing;
求交;
离散;
几何检查。

项目早期完全没有必要再造一层抽象。

但随着功能越来越多,我开始反复遇到一个问题:

不同模块都在从同一份 B-Rep 里,
重新推断自己需要的业务关系。

显示模块在推断 Face 和 Triangle 的关系。

模型修复在推断缺陷区域。

网格模块在推断哪些 Body 接触、相交或共享接口。

聚合模块又在重新判断哪些拓扑应该保留、切分或融合。

这些判断看起来属于不同功能,但底层其实有很多重叠。

于是我开始思考:

在几何内核和上层业务之间,
是不是还缺少一层长期存在的数据结构?

几何内核知道拓扑,但不一定知道业务关系

B-Rep 本身已经是拓扑结构。

这一点不能混淆。

Solid、Shell、Face、Wire、Edge、Vertex 都是明确的拓扑对象。

所以这里说的“增加一层拓扑”,不是重新发明 B-Rep。

真正缺少的是:

面向当前软件业务的稳定解释。

例如几何内核可以告诉我们:

Face A 和 Edge E 邻接;
Solid A 包含哪些 Face;
两个 Shape 做 Boolean 后生成了哪些新 Shape。

但它不会天然告诉我们:

Face A 和 Face B 是仿真接口;
这一片薄面应该被视为一个可简化特征;
这几条碎边在网格阶段应该作为同一条逻辑边;
这个接触关系必须保留,但不一定需要立刻物化成新 B-Rep;
这个区域修改以后,哪些属性需要迁移。

这些信息不是底层几何事实。

它们是建立在几何事实之上的工程语义。

我越来越不希望每个模块重新遍历 Shape

当显示、修复、聚合和网格都直接访问底层 Shape 时,工程里很容易出现重复工作。

例如为了知道两个 Solid 是否需要进一步处理,不同模块可能分别执行:

包围盒判断;
Face 遍历;
几何距离;
Section;
共享拓扑判断。

同一份信息被多次计算。

更麻烦的是,不同模块可能使用不同判断标准。

于是会出现:

修复模块认为这两个区域相关;
聚合模块认为不相关;
网格模块再次做一套自己的判断。

这类问题如果只靠“统一函数”解决,能改善一部分。

但如果关系本身需要跨流程保存,就更适合把它变成数据。

例如:

Body A
  └─ Interface 17
       ├─ Body B
       ├─ involved faces
       ├─ relation type
       ├─ confidence
       └─ current state

这时上层模块消费的是同一份关系,而不是每次从零推导。

第一层应该还是忠实表达 Topology

如果真的增加一层几何底座,我觉得第一层仍然应该非常克制。

它不应该一开始就塞入大量“智能判断”。

最基础的一层仍然是:

Topology。

也就是对底层 B-Rep 的稳定引用和组织。

它至少需要表达:

Object;
Solid;
Shell;
Face;
Edge;
Vertex;
邻接;
包含;
来源;
版本。

这层的目的不是替代 OCCT。

而是把上层业务从:

TopoDS_Shape

这种具体实现类型里逐渐解耦出来。

对业务来说,更重要的应该是:

这是哪个拓扑对象;
它和谁邻接;
当前版本是否仍然有效;
如何回到底层几何。

至于底层实际由哪个 Kernel 提供,可以晚一点再决定。

Interface 是我认为最值得单独保存的一类关系

最近在处理共形网格和多体聚合时,我越来越觉得:

Interface

不应该只是某次算法运行中的临时结果。

两个 Body 之间可能存在:

共享面;
接触;
相交;
重叠;
间隙;
近接触。

这些关系对后续流程都有影响:

是否需要切分;
是否需要共形;
是否可以保持独立;
是否存在重复体域风险;
网格约束怎样传播。

如果这些关系每次都通过 Boolean 或 Section 重新计算,成本很高。

更重要的是,它们不一定都需要立即转换成真实拓扑修改。

这让我开始逐渐接受一种思路:

先保存关系,
再决定是否物化几何。

也就是类似 Interface Graph 的结构。

节点是几何域。

边描述几何域之间的接口关系。

这样后续算法可以围绕:

Interface

做局部处理,而不是每次对整个装配体执行一次全局 Boolean。

虚拓扑的价值不是“假的拓扑”

第一次接触虚拓扑这个概念时,很容易理解成:

不修改真实模型,
只在外面记几个 tag。

但真正有价值的地方不是“省掉一次 Boolean”。

而是把:

计算需要的拓扑

和

原始 CAD 必须物化的拓扑

分开。

例如为了生成共形网格,我们可能需要知道:

这两张面逻辑上属于同一个接口;
这条长边可以在网格层视为若干逻辑分段;
这几条碎边对网格来说应该作为一条连续边。

这些关系对网格很重要。

但不一定每一个都值得修改原始 B-Rep。

如果能让 Mesh 层理解这些逻辑关系,就可以避免一部分高风险的几何重建。

当然,这要求网格器也能够消费这种拓扑语义。

所以虚拓扑并不是一个单独功能。

它更像是几何底座和网格底座之间的一份协议。

Domain 解决的是“谁属于一个计算区域”

Interface 关注关系。

Domain 更关注:

计算区域。

一个 CAD 装配里,原始零件层次不一定等于最终仿真域。

例如:

多个零件可能共同形成一个材料域;
一个空气区域可能包围多个实体;
一些几何体只是辅助构造;
某些相交体需要在仿真前拆成互斥体域。

所以:

CAD Object

和

Simulation Domain

不应该强绑定。

Domain 层如果建立起来,上层就可以围绕“仿真区域”做操作,而不是永远围绕导入时的 Solid 列表。

这一点对后续网格、材料和求解准备都很重要。

Feature 不应该只理解成建模历史特征

这里的 Feature 也不是传统参数化 CAD 里的:

Extrude;
Fillet;
Chamfer。

至少在当前阶段,我更关心几何分析得到的结构特征。

例如:

小面;
薄桥;
狭长区域;
特征边;
孔;
局部高曲率区;
可能影响网格质量的碎特征。

这些 Feature 可以服务:

修复;
简化;
网格控制;
显示标记。

把它们单独保存以后,很多算法就不必反复从 Face/Edge 重新识别。

当然,Feature 是最容易“过度智能化”的一层。

所以我认为它应该建立在稳定几何证据上,并且允许明确表达:

置信度;
来源;
失效条件。

而不能把一次启发式判断永久当成事实。

四层之间的关系

如果把目前的想法简化,我更倾向于把几何底座理解成几类不同数据:

Topology
    ↓
底层几何和拓扑事实

Interface
    ↓
对象之间的接触、相交和共享关系

Domain
    ↓
面向计算的区域组织

Feature
    ↓
面向修复、简化和网格的结构特征

它们不是严格单向层级。

更准确地说,它们是围绕同一份几何建立的不同视图。

例如:

一个 Feature

可能引用若干 Face。

一个 Interface

也引用双方的 Face。

一个 Domain

包含若干 Topology Object,并由多个 Interface 和其他 Domain 相连。

这样上层业务就不再必须从 B-Rep 的树结构出发。

Geometry Kernel 仍然应该保留在最底层

有了这些中间结构,并不意味着几何内核不重要了。

恰恰相反。

真正的:

曲线;
曲面;
求交;
Boolean;
Sewing;
几何投影;
拓扑构造;

仍然需要成熟 Kernel。

我现在更倾向于把目标理解成:

隐藏依赖

而不是:

替代几何内核。

理想状态可能是:

Application
    ↓
Geometry Intelligence Layer
    ↓
Kernel Adapter
    ↓
OCCT / Other Kernel

这样业务代码逐渐依赖自己的数据契约。

底层 Kernel 仍然负责最困难的几何计算。

这和显示引擎把 OSG 隐藏在 SDK 后面其实有些类似。

这层数据必须支持版本和失效

几何关系不是永远不变的。

一次 Boolean、Repair 或局部重建以后:

Face 可能 Split;
Edge 可能 Merge;
Solid 可能替换;
Interface 可能失效;
Feature 可能消失。

所以如果建立自己的几何底座,不能把它当成静态缓存。

必须从一开始考虑:

版本;
依赖;
失效;
局部重建;
Topology History。

否则数据量越大,旧关系越容易残留。

这种脏数据比重新计算慢更危险。

因为它可能导致错误的网格或者错误的修复判断。

现在还不适合把它写成最终架构

目前这套想法还在快速演进。

尤其是:

Interface 如何稳定定义;
虚拓扑和真实拓扑如何切换;
Domain 怎样持久化;
Mesh 到底消费哪些中间层数据;

都还需要更多案例验证。

所以我现在不会把它称为某个已经定型的“标准架构”。

更准确的说法应该是:

随着显示、修复和网格功能越来越复杂,
我们开始逐渐把重复出现的几何关系从算法内部抽出来,
形成一个长期存在的中间层。

这一步是否最终会发展成完整的几何底座,还需要继续做。

但方向已经越来越清楚。

小结

直接使用 B-Rep 并没有错。

对于很多功能,它仍然是最可靠、最清楚的方式。

真正的问题出现在:

越来越多模块开始围绕同一份 B-Rep,
重复推导相同或相似的工程关系。

这时继续堆工具函数,能够解决局部问题,但很难形成长期稳定的数据基础。

所以我现在越来越希望在几何内核之上逐渐建立几类自己的表达:

Topology 表达几何事实;
Interface 表达对象关系;
Domain 表达计算区域;
Feature 表达工程特征。

底层 Kernel 继续负责真正的几何计算。

上层业务逐渐依赖自己的稳定语义。

如果这条路最终能够走通,那么显示、修复、聚合和网格就不再只是几套各自操作 Shape 的模块,而会开始共享同一份几何理解。

对我来说,这可能比再实现一个新的 Boolean 或 Repair 算法更重要。