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 模型修复最明显的一次认识变化。