Technical note

从几何修补到修复系统:一次 CAD 模型修复架构的重新设计

记录 CAD/CAE 模型修复从单个 Repair API 逐渐演进为检测、区域分析、策略选择、真实预览、验证和事务提交完整链路的过程,以及为什么复杂模型修复不能只依赖一键算法。

背景:模型修复最早看起来很像一组工具函数

刚开始做 CAD 模型修复时,我对这件事的理解比较接近传统几何库的使用方式。

检测到问题以后,根据问题类型调用对应函数:

有自由边 → Sewing;
有缺口 → Fill;
有小边 → Remove;
有坏 Wire → FixWire;
有坏 Face → FixFace。

如果算法返回成功,再把新 Shape 替换回模型。

这种方式在简单模型上确实能解决不少问题。

但随着模型变复杂,尤其开始面对真正影响网格和仿真的问题以后,我越来越发现:

修复的难点并不是“有没有一个函数可以调用”,
而是“系统凭什么判断这次修改应该被接受”。

一个几何函数返回了一个合法 Shape,并不代表原问题已经解决。

更不代表没有制造新的问题。

这也是后来整个模型修复模块开始大改的原因。

Issue 只是证据,不是修复单位

检测模块最自然的输出是一条条 Issue。

例如:

FreeEdge;
SmallEdge;
SlenderFace;
FaceGap;
SelfIntersection;
MissingPCurve;
MeshConformityRisk。

但真实缺陷往往不会严格对应一条 Issue。

一个局部几何问题可能同时表现为:

几条自由边;
一张狭长面;
一个曲面间隙;
若干网格风险。

如果用户逐条修,系统就很容易对同一片区域重复操作。

所以后来我开始把 Issue 理解为:

检测证据。

而真正的修复工作单元应该是:

问题区域。

这个区域不只是“附近几张 Face”。

它还需要知道:

真正的问题核心在哪里;
算法允许修改到哪里;
哪些边界是局部修复接口;
哪些区域必须完全保持。

于是逐渐形成:

Core
Influence
Anchor
Preserve

这样的区域角色。

这一步以后,修复系统的重点就从“选哪个函数”变成了“先确定修改边界”。

为什么修复范围必须显式表达

CAD 修复最危险的一类情况是:

问题看起来解决了,
但算法顺便修改了更大范围。

例如为了消除一条坏边,算法重建了邻接面。

邻接面重建以后,又影响了周围更多 Face。

如果系统只比较最终 Shape 是否有效,就很难知道修改有没有越界。

所以我后来越来越坚持:

局部修复必须先定义局部。

也就是在运行算法之前先回答:

哪里允许变;
哪里只能作为支撑;
哪里绝对不能变。

这让很多“自动修复”变得更保守。

但我认为这是值得的。

因为在 CAE 模型里,一张 Face 可能还关联:

材料;
边界条件;
端口;
网格控制;
命名选择。

几何修改范围一旦失控,问题就不再只是 B-Rep。

Analyzer 和 Executor 必须分开

以前一个 Repair 函数通常同时完成:

判断;
执行;
返回结果。

后面我觉得这种结构不适合复杂修复。

更合理的是把“能不能修”和“真正怎么修”分开。

Analyzer 负责:

当前区域是什么问题;
证据够不够;
哪些策略可能适用;
哪些条件不满足;
为什么拒绝某个方案。

Executor 才负责:

生成真实候选;
完成必要重建;
输出结果。

这两个阶段分开以后,一个很重要的变化是:

系统可以明确说“我知道这个问题是什么,但现在不应该自动修改”。

以前这种情况往往只能表现成:

Repair Failed。

现在则可以给出更明确的拒绝原因。

对复杂几何来说,我觉得“正确拒绝”本身就是一种能力。

Preview 不能只是高亮影响区域

交互式修复里,Preview 很容易做成:

先高亮可能修改的 Face;
用户点击确认;
再真正执行一次算法。

这种方式界面看起来完整,但在几何修复里有一个问题。

用户看到的并不是最终结果。

如果确认后重新执行算法,那么:

输入顺序;
容差;
当前模型版本;
算法内部状态;

都有可能让第二次结果和第一次不同。

所以后来我更倾向于使用真实候选预览。

流程更接近:

Baseline
    ↓
生成 Candidate
    ↓
Validation
    ↓
Prepared Transaction
    ↓
Preview Candidate
    ↓
Apply

Apply 时不再重新计算。

真正提交的就是用户已经看到的那份 Candidate。

这让 Preview 从视觉效果变成了事务的一部分。

修复成功不能只看 BRepCheck

B-Rep 有效当然是最基本的条件。

但对于模型修复,它远远不够。

一个结果可能:

BRepCheck 通过;

同时:

原来的自由边还在;
新增了其他自由边;
体积发生异常变化;
某些 Preserve Face 被替换;
表面网格仍然失败。

所以后来的验证逐渐分成几层。

第一层是基础几何有效性:

Shape 非空;
B-Rep 合法;
基本容器结构没有异常。

第二层是目标问题:

原来想修的问题是否真的减少或消失。

第三层是新增问题:

有没有产生新的自由边、非流形或其他阻断问题。

第四层是区域约束:

Anchor 是否保持;
Preserve 是否没有被意外修改。

第五层是下游可用性:

至少重新生成表面网格;
必要时进入更严格的网格验收。

这样才能回答:

结果是不是“真的更好”。

而不是只回答:

算法有没有返回一个 Shape。

检测驱动比按钮驱动更适合修复

早期产品界面很容易变成:

Sew;
Fill;
Remove;
Rebuild;
Fix。

用户需要知道应该按哪个按钮。

但对真正复杂的问题来说,用户通常看到的是:

这里网格失败了;
这里有一段异常交线;
这里存在很窄的桥接面;
这里局部拓扑很复杂。

他并不一定知道底层需要调用哪个几何算法。

所以后来整个方向逐渐变成:

Detection
    ↓
Issue
    ↓
Region
    ↓
Strategy
    ↓
Preview
    ↓
Validation
    ↓
Commit

用户操作的是问题,而不是 Repair API。

这是一次比较重要的变化。

因为它意味着修复模块开始从“工具箱”转向“问题处理系统”。

有些成功案例反而让我更谨慎

这段时间我们已经能够在一些局部问题上形成完整闭环。

例如:

小特征;
局部窄桥接区域;
部分开放边界问题;
特定场景下的面重建和缝合。

也有复杂模型通过:

问题检测;
炸体为面;
特征线提取;
Edge 拓扑调整;
面重构;
重新缝合;
体重建;

最终重新进入网格流程。

这些案例证明整个方向是有效的。

但它们没有让我觉得“自动修复已经完成”。

恰恰相反,它让我更明显地看到:

一个成功案例背后,
通常有非常具体的几何条件。

把这些条件去掉,算法很可能就不再可靠。

所以公开一个修复能力时,我现在更关心:

安全边界是什么;
拒绝条件是什么;
验证做到了哪一层。

而不是只展示成功截图。

Topology History 逐渐变成核心数据

模型修复还有一个很容易被低估的问题:

新 Shape 生成以后,
旧模型上的业务数据怎么办?

如果一张 Face 被 Split:

原来的材料应该复制给哪一张;
边界条件是否两张都继承;
命名选择如何更新。

如果几张 Face 被 Merge:

原来不同属性发生冲突怎么办。

所以修复执行器不能只返回:

newRootShape。

还需要描述:

Modified;
Split;
Merged;
Deleted;
Generated。

也就是拓扑 History。

我现在越来越觉得,这份 History 的重要性甚至不低于修复后的几何本身。

因为没有它,几何虽然修好了,上层模型却可能已经失去语义连续性。

事务层解决的是“半成功”

多对象或者复杂修复最怕一种状态:

几何 A 已经提交;
几何 B 失败;
属性只迁移了一半;
显示层又已经刷新。

这种“半成功”通常比直接失败更麻烦。

所以修复开始引入事务以后,一个基本要求就是:

要么全部通过;
要么什么都不改。

这意味着提交前必须确认:

Baseline 没有变化;
Candidate 仍然有效;
Validation 仍然通过;
属性迁移条件满足;
所有参与对象都可提交。

如果其中任何一步失败,就应该回到原状态。

这类机制本身不生成几何,但它决定了修复功能是否真的适合进入正式工程。

当前能力边界

现在的修复系统已经比最开始的单函数模式完整很多,但仍然有明显边界。

第一,一般曲面重建仍然很难。

平面、圆柱、自由曲面之间的组合,需要不同的求交、裁剪、PCurve 和连续性处理。

第二,复杂自相交仍然需要更明确的区域分解。

尤其是多段交线、多孔区域和近相切情况。

第三,小特征和非流形问题还需要继续形成各自稳定的 Analyzer 和 Executor。

第四,真正的生产网格验收还应该继续接入。

表面网格成功并不等于最终体网格一定成功。

第五,跨版本持久引用仍然是长期问题。

修复以后拓扑变化越大,旧 Face/Edge 引用就越难稳定恢复。

这些都不适合用一个“一键修复”按钮掩盖过去。

小结

这次模型修复模块最大的变化,并不是多实现了几个 Repair 算法。

真正的变化是整个问题开始被拆成不同责任:

Detection 负责发现证据;
Region 负责确定修复范围;
Analyzer 负责判断适用性;
Executor 负责生成真实候选;
Validation 负责证明结果更好;
Transaction 负责保证提交原子性;
Topology History 负责连接新旧语义。

这条链路比一个修复函数复杂很多。

但我现在越来越觉得,复杂模型修复如果想真正进入工业软件,就很难绕过这些步骤。

几何修补解决的是:

能不能生成一个新 Shape。

修复系统需要继续回答:

为什么要改;
应该改哪里;
凭什么接受;
失败以后怎样回去;
上层语义怎样继续存在。

这也是这次重构以后,我对 CAD 模型修复最明显的一次认识变化。