Technical note

交互式模型修复

记录一次 CAD/CAE 交互式几何修复流程的工程思路:用户选择边或面后,系统如何构建局部上下文、识别问题、生成预览、执行局部修复,并通过验证、报告和撤销保证修复可控。


背景

前面几篇文章里,已经分别梳理了 OCCT 常用修复接口、几何修复模块为什么要分层,以及 Sewing 和 free edge 修复的工程边界。

这些内容更多是在讲“修复能力”。但真正落到 CAD/CAE 软件里,还会遇到另一个问题:

修复能力应该怎么交给用户使用。

几何修复并不总是适合全自动执行。

有些问题可以在导入阶段轻量处理,比如基础 ShapeFix、简单拓扑整理、基础有效性检查。

但有些问题风险更高,例如局部补面、面缝合、开口闭合、近接触处理、局部拓扑替换。这些操作可能改变模型拓扑,也可能影响后续网格、边界条件、材料区域和用户选择对象。

如果这类修复在后台静默完成,用户很难判断系统到底改了什么。后续一旦模型出问题,也很难回溯。

所以对于复杂几何修复,我更倾向于做成交互式流程:

用户选择问题区域;
系统识别局部问题;
生成候选修复;
显示预览和风险;
用户确认;
执行局部替换;
输出修复报告;
支持撤销或回滚。

这篇文章主要记录我对交互式模型修复流程的理解。

自动修复和交互式修复

自动修复和交互式修复不是谁替代谁的问题,它们适合的场景不同。

自动修复更适合:

导入阶段的轻量修复;
批量模型的基础清理;
明确低风险的问题;
不会改变模型语义的局部规整;
可以失败但不影响主流程的尝试。

交互式修复更适合:

需要用户确认的问题;
局部补面;
局部 Sewing;
开放边界闭合;
面缝隙修复;
近接触和弱相交处理;
可能改变拓扑语义的操作。

自动修复追求的是效率和批处理能力。

交互式修复追求的是局部可控、结果可解释、失败可回滚。

CAD/CAE 场景里,后者非常重要。因为模型修复不是普通的数据清理,它经常会影响后续仿真流程。

一个看起来很小的拓扑变化,可能导致网格区域变化、边界条件丢失、接触关系改变,甚至影响结果复查。

因此,复杂修复不应该只隐藏在一个 repairAll() 里。用户应该能看到系统发现了什么问题、准备怎么修、修完以后发生了什么变化。

入口是用户选择

交互式修复的第一步不是修复,而是选择。

用户通常不会直接告诉系统“这里是某种 Issue,使用某种 Strategy”。更自然的操作是:

选中一条自由边;
框选几条边界;
选择几张面;
选择一个开口区域;
点击系统检测出来的问题标记。

系统需要把这些选择转换成修复上下文。

例如用户选择几条边,系统要判断:

这些边是否属于同一个局部区域;
是否能组成连续边界;
是否能形成闭合 wire;
是否关联同一组 face;
是否可能是 open boundary;
是否适合补面或 Sewing。

用户选择几张面,系统要判断:

这些面是否相邻;
是否存在未连接边界;
是否可以组成局部 shell;
是否存在自由边;
是否适合做局部 Sewing;
是否需要加入邻域 face。

所以交互式修复的输入不是简单的 TopoDS_EdgeTopoDS_Face 列表,而是带语义的局部选择上下文。

可以把它理解成:

Selection

Local Context

Issue Detection

Repair Candidate

这一步做得越清楚,后面的修复越可控。

构建局部上下文

用户选中的对象往往不够完整。

例如用户只选了几条边,但修复时可能还需要这些边所在的 face、相邻 face、局部 shell、所属 solid,以及当前模型的拓扑关系。

如果只拿用户选中的 edge 直接补面,很容易缺少上下文。

所以系统需要从选择对象扩展出局部上下文。

一个典型流程是:

读取用户选择的 edge / face;
查找这些对象所属的 shape;
构建局部拓扑映射;
找到相关 face;
找到相邻 face;
统计局部 free edge;
构造局部 patch;
生成问题描述。

这里的重点是“局部”。

交互式修复不应该一上来就把整个模型拿去修,而是围绕用户选择的区域构造一个有限范围。

局部上下文通常包含:

用户直接选择的边或面;
这些边所属的 face;
邻近 face;
局部 free edge;
局部 open boundary;
原始 shape 引用;
可替换的局部 patch;
修复后需要回写的位置。

这样修复流程就不会脱离用户意图。

先识别问题

用户选择边界,不代表一定要补面。

用户选择面,也不代表一定要 Sewing。

交互式修复里仍然需要问题识别。

例如用户选择了一圈边,系统要判断:

边界是否闭合;
边界是否自交;
边是否方向一致;
边界是否共面;
是否存在明显 gap;
是否对应已有相邻面;
是否真的缺失面。

如果用户选择了几张面,系统要判断:

这些面之间是否已经连接;
是否存在 free edge;
是否存在多个不连通区域;
是否能组成 shell;
Sewing 后是否可能改善。

这一步的作用是避免误修。

例如一圈边看起来像孔洞,但如果它是开放曲面的自然边界,就不应该自动补面。

几张面看起来相邻,但如果它们属于不同零件,就不应该直接 Sewing 成一个整体。

所以交互式修复不能把用户选择简单翻译成某个固定 API 调用。它应该先把选择转成问题,再由策略层决定修复方式。

给出候选修复

识别问题后,系统可以生成候选修复方案。

例如:

当前区域像是拓扑未连接,建议尝试局部 Sewing;
当前边界形成闭合 loop,建议尝试局部补面;
当前 wire 不稳定,建议先做 wire 修复;
当前区域无法安全判断,建议用户重新选择;
当前问题可能是开放边界,不建议自动闭合。

这类候选修复比直接执行更好。

因为它让用户知道系统的判断依据。用户不需要理解所有 OCCT 细节,但至少应该知道系统为什么推荐 Sewing,为什么推荐补面,或者为什么拒绝自动修复。

一个候选修复可以包含:

问题类型;
推荐策略;
参与修复的 face / edge 数量;
可能生成的新 face 数量;
预计影响范围;
风险提示;
是否支持预览;
是否支持回滚。

这比一个简单的“修复”按钮更符合工程软件的交互逻辑。

先预览,再替换

对于高风险修复,系统最好先生成预览,而不是直接替换原 shape。

预览可以让用户看到:

哪些边会被缝合;
是否会生成新面;
新面的大致位置;
局部 open boundary 是否减少;
哪些面会被替换;
修复后的局部 patch 长什么样。

在显示上,预览可以用临时颜色、高亮边界、半透明面或 overlay 标记表示。

用户确认后,再真正写回模型。

这个流程在交互式修复里很重要。因为几何修复有时候不是绝对正确或错误,而是“是否符合用户意图”。

系统生成的补面可能从数学上可行,但用户不一定希望这样补。

Sewing 可能让局部拓扑闭合,但用户可能希望保持两个区域分离。

预览给了用户确认的机会,也给系统提供了回滚边界。

执行要限制范围

用户确认后,系统才进入真正执行阶段。

执行阶段要避免两个问题。

第一,不要对整个模型做无差别修复。

交互式修复已经提供了局部上下文,如果最后仍然对全局 shape 做强修复,就失去了交互式流程的意义。

第二,不要只返回一个新 shape 就结束。

修复后还要把局部结果安全地放回原模型,并更新相关显示、选择和拓扑映射。

一个局部修复流程大致是:

保存原始局部 patch;
基于候选策略执行修复;
得到局部修复结果;
验证局部结果;
替换原 shape 中对应部分;
更新拓扑映射;
更新显示对象;
生成修复报告;
写入撤销栈。

这里最容易被低估的是“替换回原模型”。

例如补面本身可以通过 BRepFill_Filling 完成,Sewing 可以通过 BRepBuilderAPI_Sewing 完成。

但修复后的局部 patch 怎么回到原模型,原来的 face / edge 引用怎么维护,后续选择对象怎么更新,这些都不是 OCCT 单个 API 自动完成的。

这也是交互式修复需要局部拓扑编辑能力的原因。

Sewing 命令

局部 Sewing 是交互式修复里最自然的一类命令。

用户可能选中几张面,或者选中某个系统标记出的 open shell 区域,然后尝试把这些面缝成一个更完整的局部 shell。

典型流程是:

用户选择 face;
系统提取局部 face 集合;
统计修复前 free edge;
执行局部 Sewing;
统计修复后 free edge;
检查 shape validity;
生成预览;
用户确认后替换局部 patch。

这里 Sewing 的关键不是 API,而是输入范围和结果判断。

如果用户只选了两张面,但实际需要第三张相邻面作为上下文,系统可以提示用户扩大选择,或者自动加入一层邻域 face 作为候选。

如果 Sewing 后 free edge 没有减少,就不应该提示修复成功。

如果 Sewing 后 shape 有效性变差,应该回滚。

这类命令的报告可以很简单:

参与 Sewing 的 face 数量;
修复前 free edge 数量;
修复后 free edge 数量;
是否生成 shell;
是否仍存在 open boundary;
是否接受结果。

这已经比一个单纯的成功提示有用很多。

补面命令

补面类命令通常由用户选择边界开始。

用户可能选中几条边,系统尝试判断这些边是否能组成一个闭合边界,然后生成修补面。

流程大概是:

用户选择 edge;
系统尝试排序成 wire;
检查 wire 是否闭合;
检查边方向和连接关系;
判断边界是否适合补面;
生成 patch face;
把 patch face 和邻域 face Sewing;
验证 free edge 是否减少;
显示预览;
用户确认后写回模型。

这类命令比 Sewing 风险更高,因为它会新增几何。

因此,补面类命令更应该依赖预览和确认。

系统应该明确告诉用户:

当前边界是否闭合;
补面是否成功;
生成了几张新面;
新面是否和周围面连接;
修复后是否仍存在自由边;
是否建议继续手动处理。

如果边界不稳定,例如不闭合、自交、方向混乱,系统应该拒绝自动补面,而不是强行生成一个不可解释的面。

Wire 修复命令

有些时候,用户选择了一圈边,但系统无法直接补面。

问题可能出在 wire 本身。

例如:

边界有小 gap;
边顺序不连续;
存在重复边;
存在过短边;
方向不一致;
边界局部自交。

这时候可以先提供 wire 修复候选,而不是直接补面。

Wire 修复的交互流程可以是:

用户选择边界;
系统识别 wire 问题;
显示断点或 gap 位置;
尝试局部连接或规整;
生成修复后的 wire 预览;
用户确认后进入补面或 Sewing。

这种流程的优点是把问题拆开了。

用户不会看到一个模糊的“补面失败”,而是能知道失败原因可能是边界本身不稳定。

系统也可以把 wire 修复作为补面前的准备步骤,而不是把所有逻辑塞进补面函数里。

面贴合命令

近接触和面贴合比 Sewing、补面更敏感。

因为它们处理的问题不一定是 shape invalid,而是后续网格、接触关系或共形界面的风险。

例如两个面非常接近,但没有真正相交,也没有共享拓扑。这种情况可能导致网格生成困难,也可能导致共形界面不明确。

但它不一定应该被 Sewing。

两个面可能属于不同零件,可能应该保持间隙,也可能需要用户确认后做最小必要调整。

所以这类修复最好作为交互式命令,而不是自动修复。

公开讨论时,可以把它描述成:

识别近接触区域;
生成候选调整方向;
估计最小必要位移;
评估风险是否下降;
显示修复前后对比;
用户确认后执行局部调整。

这里不适合展开内部算法细节。

更重要的是工程原则:

只处理局部区域;
只做最小必要修改;
不静默改变接触关系;
修复前后要能验证;
用户必须能确认或回滚。

这类命令最能体现 CAD/CAE 前处理和普通 CAD 建模的区别。

它不是单纯为了让 shape valid,而是为了让模型更适合后续网格和仿真。

报告面向用户

交互式修复完成后,系统应该输出修复报告。

这个报告不一定要很复杂,但至少应该让用户知道:

修复了哪个区域;
使用了什么策略;
修复前有哪些问题;
修复后问题是否减少;
是否生成新面;
是否还有残留问题;
是否建议继续处理。

内部日志和用户报告不应该混为一谈。

内部日志可以记录更多细节,例如耗时、容差、调试信息、失败路径。

用户报告应该更关注修复效果和风险。

例如用户更关心的是:

这次修复有没有改变模型;
哪里被改变了;
还有没有自由边;
这个结果能不能继续网格;
是否可以撤销。

而不是底层 API 调用了几次。

这也是交互式修复比自动修复更需要报告的原因。因为用户参与了选择和确认,也需要看到结果反馈。

撤销和回滚

几何修复一定要支持撤销。

尤其是补面、局部替换、面贴合这类操作,用户确认后也可能发现结果不符合预期。

如果没有撤销能力,用户会不敢使用修复工具。

从工程实现上看,撤销至少需要保存:

修复前的原始 shape;
修复后的新 shape;
受影响的局部对象;
显示和选择状态;
修复报告;
必要的拓扑映射更新信息。

如果支持局部回滚,则还需要记录局部 patch 的替换关系。

这里要注意一点:修复失败时也应该能回滚。不能因为局部修复执行到一半失败,就留下一个半更新状态。

所以交互式修复执行时,最好把修改过程做成事务式:

准备修复数据;
生成候选结果;
验证结果;
全部通过后提交;
任何一步失败都回滚。

这样用户体验会稳定很多。

显示和选择也在流程里

交互式修复不是纯几何内核问题,它还涉及显示和选择。

用户选择边或面时,系统需要高亮问题区域。

系统生成修复候选时,需要显示预览。

修复完成后,需要更新场景中的显示对象和选择映射。

如果拓扑发生变化,原来的 face / edge 选择关系也可能失效。

所以修复流程不能只考虑 TopoDS_Shape,还要考虑:

用户选中的对象是否仍然存在;
新生成的 face 是否能被选择;
被替换的 face 是否从显示中移除;
高亮和预览是否清理;
工程树或属性面板是否更新;
撤销后显示状态是否恢复。

这类细节虽然不属于 OCCT API,但对于交互式修复非常关键。

用户感受到的不是“底层 shape 是否修好了”,而是整个操作是否连贯、可理解、可恢复。

为什么需要统一调度

交互式修复看起来每个命令都不同:Sewing、补面、Wire 修复、面贴合。

但它们的流程其实很像:

读取用户选择;
构建局部上下文;
检测问题;
生成候选策略;
执行修复;
验证结果;
更新模型;
更新显示;
生成报告;
支持撤销。

所以这些命令不应该各自从头实现一遍。

更合理的做法是把通用流程收敛到统一调度层,把具体差异放到专项修复工具里。

例如:

交互命令层:处理用户输入和 UI 反馈;
修复调度层:组织检测、策略、执行、验证;
专项工具层:负责 Sewing、Filling、Wire 修复、局部替换;
显示层:负责高亮、预览、刷新;
文档层:负责提交、撤销和报告。

这样后续新增修复能力时,不需要重复处理撤销、报告、预览和局部上下文。

这也是几何修复模块分层在交互式场景里的具体价值。

一个完整流程

把前面的内容合在一起,一个比较完整的交互式修复命令可以设计成这样:

1. 用户选择边或面
2. 系统构建局部拓扑上下文
3. 检测 free edge / gap / open boundary 等问题
4. 对问题进行分类
5. 生成一个或多个候选修复策略
6. 显示修复预览和风险提示
7. 用户确认
8. 执行局部修复
9. 验证修复前后变化
10. 提交模型修改
11. 更新显示和选择映射
12. 输出修复报告
13. 支持撤销

这个流程看起来比直接调用 API 慢一些,但对复杂 CAD/CAE 系统更可靠。

因为它把用户意图、几何修复、显示反馈和文档状态串在了一起。

用户不是被动等待系统黑盒修复,而是能理解并控制局部修改。

小结

交互式模型修复最重要的不是“把按钮做出来”,而是把修复流程做清楚。

我现在更认可的判断是:

用户选择不是修复策略,只是修复上下文;
系统需要先识别问题,再生成候选方案;
Sewing、补面、Wire 修复都应该有预览和验证;
高风险修复必须支持用户确认;
局部修复应该优先于全局强修复;
修复结果应该有报告;
修复操作必须支持撤销和回滚;
显示、选择、文档状态都属于交互式修复的一部分。

这套流程的目的不是让修复变复杂,而是让修复变得可信。

对于 CAD/CAE 软件来说,用户真正需要的不是一个“自动修好”的神奇按钮,而是一个能告诉他问题在哪里、准备怎么修、修完是否变好的工具。

几何修复涉及太多不确定性。同样一条 free edge,可能是拓扑未连接,也可能是缺面,也可能是开放边界。同样一个近接触区域,可能是错误,也可能是设计意图。

因此,复杂修复不应该完全静默自动化。

交互式修复的价值在于:

让用户指定局部区域;
让系统提供修复判断;
让修复过程可以预览;
让结果可以验证;
让操作可以撤销;
让模型变化可以解释。

从这个角度看,交互式模型修复不是自动修复的补充,而是 CAD/CAE 几何修复里非常重要的一条主线。

OCCT 提供了 Sewing、Filling、ShapeFix 等底层能力,但真正把这些能力变成可用功能的,是选择、检测、预览、局部替换、验证、报告和撤销这一整套工程流程。