Technical note
模型修复为什么要限制修改范围
记录一种 CAD/CAE 区域化模型修复思路:从问题种子构造局部修复区域,在受控邻域内执行、预览、替换和验证,并在局部策略不适用时安全回退。
背景:问题往往是局部的,修改却经常是全局的
做 CAD/CAE 模型修复时,经常会遇到一种不对称:
真正有问题的可能只有一个小区域;
但底层修复函数接收的是整个 root shape;
最后返回的也是一个新的 root shape。
例如一个包含几千张面的模型,可能只有两张相邻面之间存在微小缝隙。为了修这一个缝隙,如果把整个模型都交给 Sewing、ShapeFix 或小面清理算法,系统会让大量无关拓扑一起进入计算。
这样做实现起来比较直接,但风险也很明显:
计算范围被放大;
无关区域可能被重新组织;
原有 edge / face identity 可能变化;
用户选择和边界条件映射更难保持;
修复失败时只能回滚整个模型;
很难解释系统到底改了哪里。
更麻烦的是,全局修复可能把一个局部问题转换成另一个区域的问题。
例如为了缝合局部自由边而增大全局容差,其他零件之间原本应该保留的近接触边界,也可能被错误合并。为了删除一个小面而执行全局清理,另一个对仿真边界有意义的小特征,也可能被一起删除。
所以我后来更倾向于把模型修复换一个角度理解:
先确定问题区域;
再为这个区域补充必要上下文;
只在受控范围内生成修复结果;
最后把局部结果安全放回原模型。
这就是区域化修复想解决的问题。
它不等同于某一个 OCCT API,也不只是“用户选几张面”。它更像是一套围绕影响范围建立的修复事务。
区域化修复不是简单选几张面
区域化修复的核心,不是把全局函数的输入从一个 shape 换成几张 face。
如果只是把用户选中的几张面取出来,单独执行一个修复函数,最后再尝试拼回去,很多问题仍然没有解决。
真正困难的是下面这些事情:
如何从检测问题或用户选择得到一个可靠的区域种子;
如何扩展必要邻域,又不把局部范围无限放大;
如何区分参与计算的上下文和真正允许修改的核心区域;
如何让局部算法的输出重新嵌回 root shape;
如何验证局部问题改善,同时确保全局模型没有退化;
局部路径失败以后,什么时候扩大区域,什么时候转成全局修复,什么时候应该停止。
从这个角度看,一个完整的区域化修复流程更像是:
Detect / Select
↓
Build Region
↓
Classify & Choose Strategy
↓
Preview
↓
Execute in Region
↓
Replace Region
↓
Local + Global Validation
↓
Accept / Rollback / Expand / Fallback
其中最难的通常不是 Execute in Region。
真正难的是区域怎么建、边界怎么守、结果怎么放回去,以及怎么判断这次局部修改确实比原模型更好。
一个修复区域应该包含什么
修复区域不能只用一个 face 列表来定义。
一个更完整的区域,至少应该包含几类对象。
问题种子
问题种子是区域的起点,可以来自自动检测,也可以来自用户选择。
例如:
一组 free edge;
一条 open boundary chain;
一个 non-manifold edge;
一张 self-intersection face;
一组 gap pair;
一个共形风险区域;
用户选择的 face、edge 或 point。
种子回答的是:
为什么要在这里开始修?
如果没有问题种子,局部范围就只是一个任意选区,很难建立目标问题的修复前后对比。
核心修改区
核心修改区是允许算法真正改变的对象集合。
例如面缝隙修复里,核心区可能是缝隙两侧的面;补面流程里,核心区可能是 open boundary 和新生成的 patch;小面清理里,核心区可能是目标小面及其直接相邻对象。
核心区需要尽量小,因为它决定最终替换和拓扑映射的范围。
区域化修复要避免的,正是“为了修一个小问题,把大范围无关拓扑一起改掉”。
上下文邻域
局部算法通常不能只看问题本身。
例如修复一条自由边时,需要知道它属于哪张面、相邻边如何连接、附近是否存在另一条可配对边、局部面法向和曲率如何变化。
所以区域还需要一圈或多圈邻域:
edge -> owner face;
face -> shared edge neighbor;
boundary -> surrounding faces;
patch -> host faces;
tool -> target neighborhood。
上下文邻域可以参与判断和计算,但不一定允许被最终修改。
这是区域化修复里一个很重要的区分:
参与计算的对象,不等于允许替换的对象。
如果没有这个区分,局部修复很容易变成披着局部外衣的全局修改。
保护区
区域里还应该允许标记不能修改的对象。
例如:
端口面;
载荷面;
材料分界面;
接触面;
用户锁定面;
带有关键命名或属性的 face;
不同零件之间只允许保持接触、不允许缝合的边界。
如果算法需要修改保护区才能继续,就应该停止、请求确认或切换策略,而不是静默扩大范围。
这类保护语义对 CAE 软件尤其重要。因为模型上的某些面,不只是几何面,还可能承载边界条件、材料、激励、载荷或后续网格区域。
区域边界
区域边界是局部结果重新接回原模型时的接口。
它需要回答:
哪些 edge / wire 连接修改区和保留区;
这些边界在修复前后的几何位置是否一致;
方向和参数范围是否兼容;
是否允许在给定容差内重缝;
是否出现了新的自由边或 T-junction。
如果区域边界没有定义清楚,局部算法即使在内部生成了一个有效 patch,也可能无法安全回装。
所以区域化修复真正要稳定下来,不能只传一组 face。它需要明确 seed、core、context、protect 和 boundary 这些语义。
区域从哪里来
区域化修复通常有几种入口。
第一种是检测驱动。
自动检测已经定位到问题 shape 时,可以从问题直接构造区域。例如检测到自由边以后,找到 owner face,沿边界链收集同一个 open region,再扩展必要的相邻 face,形成一个面缝隙修复区域。
这种方式适合专项自动修复。
它的优势是可以批量处理,目标问题也容易统计。缺点是检测结果未必包含足够的业务语义。比如一个 open boundary 到底是缺陷,还是设计上允许的开口,系统不一定能自动判断。
第二种是用户选择驱动。
交互式修复可以直接从用户选择开始。用户选择本身提供了一个非常有价值的语义:
用户希望系统关注这里,而不是整个模型。
但用户选择通常只是种子,不应该被直接当成完整输入。
选中一条 edge,不代表只修这一条 edge;选中一张 face,也不代表算法不需要周围面。系统仍然需要自动补充 owner、相邻面、边界链和保护关系。
第三种是混合入口。
更实用的方式往往是:
用户选中一个大致区域;
系统在选区内运行检测;
根据检测结果生成一个或多个问题 region;
用户确认要处理的 region;
系统给出对应候选策略和预览。
这种方式把用户语义和自动检测结合起来。
用户不需要精确选中每一条问题边,系统也不会在整个模型上无限搜索和修改。
区域不能无限扩张
局部修复经常需要扩邻域,但邻域扩展必须有停止条件。
如果只按拓扑连通不断扩展,一个小问题最终可能把整个 shell 都纳入区域。这样虽然名义上还是“局部修复”,实际上已经退化成全局处理。
比较合理的区域预算可以同时考虑:
邻域 ring 数;
最大 face 数;
最大 edge 数;
区域 bbox 与模型 bbox 的比例;
区域是否跨越不同 part;
是否碰到保护区;
边界链是否已经闭合;
目标问题是否都被覆盖。
这里的 ring 数和数量阈值不应该被当成固定答案。不同模型尺度、拓扑密度和缺陷类型需要不同预算。
重要的是,区域构造器必须有预算,而不是只要算法需要就继续扩大输入。
我更倾向于把区域扩张理解成一个受控过程:
从问题种子开始;
补充必要上下文;
遇到保护区或跨零件边界时停止;
超过预算时停止;
无法形成稳定边界时停止;
需要扩大影响范围时显式提示用户。
这比“修不动就加大范围和容差继续试”更安全。
模型修复里,有时候停止比继续修改更重要。
区域只限制范围,不能替代策略判断
区域化修复不是一种具体算法,而是一种调度方式。
同一个区域可能对应多种修复策略。
以自由边和面缝隙为例,局部区域建立以后,系统还要先判断问题属于哪一类:
已有面只是拓扑未连接;
边界几何存在小偏差;
wire 本身不闭合;
确实缺少一张面;
这是一个本来就应该保留的开放边界;
这是不同零件之间的 near contact。
对应策略也不同:
轻量 Sewing;
wire / edge geometry 修复;
局部容差调整;
局部补面后再 Sewing;
只报告不修改;
转入接触或共形关系处理。
所以区域只回答“在哪里处理”,不能替代“应该怎么处理”的分类。
一个合理的候选策略,至少应该描述:
目标 region;
问题类型;
建议策略;
风险等级;
可能改变的 face / edge;
需要保护的边界;
预览方式;
执行后如何更新模型对象。
这也是为什么区域化修复和候选策略系统很适合放在一起。
区域提供上下文,候选策略提供风险解释和执行建议,执行层只负责运行已经确认的策略。
几个适合区域化的场景
区域化修复不是所有问题的答案,但有些场景确实很适合优先尝试。
面缝隙与自由边
这是最适合区域化的场景之一。
一个可能的流程是:
检测 gap / free edge region;
收集两侧 owner face 和必要邻域;
判断是未缝合、wire 问题还是缺面;
先尝试小容差局部 Sewing;
必要时修 edge / wire;
确实缺面时生成 patch;
把 patch 与邻域面重新 Sewing;
统计修复前后 free edge 和 open boundary;
通过后替换局部区域。
它比对整个 root shape 反复 Sewing 更容易控制容差,也更容易解释修复结果。
更重要的是,它能避免因为一个局部缝隙而把整个模型都交给全局 Sewing,降低误合并其他近接触区域的风险。
小面、小边和狭长面
小特征清理尤其需要保护区和局部范围。
模型中有些小面只是导入噪声,有些却代表真实倒角、端口、载荷面或材料分界。
如果全局按同一阈值删除,很容易误伤。
区域化流程可以先把检测到的小特征聚成 region,再结合局部尺寸和业务属性判断:
是否允许删除;
删除后由哪些相邻面延拓;
局部边界是否还能闭合;
是否会改变关键选择面;
是否需要用户确认。
小特征处理最怕“看起来很小,所以直接删”。在 CAE 流程里,小并不一定代表不重要。
共形风险和局部贴合
共形问题常常只发生在接触附近。
如果能够从风险检测中得到受影响 face 集合,就可以只把这些 face 和直接邻接上下文交给局部处理,而不是对整个装配体做全局压印或切分。
但这类操作要特别注意 tool / target 语义。
参与相交计算的邻近对象,可能只是工具对象,不应该全部被当成当前对象的替换结果。区域化共形修复最终需要明确谁被更新、谁只参与计算,以及多对象结果如何在同一个事务中提交。
投影、压印、局部贴合这类操作也类似。它们天然是 tool-target 模式,不适合简单表达成“修复一个 root shape”。
真正困难的是把局部结果放回去
很多局部修复示例做到生成新 face、新 shell 或新 patch 就结束了。
但在完整模型里,生成局部结果只完成了一半。
系统还要决定如何替换原区域。
一个局部替换事务至少需要这些信息:
原 root shape;
原区域的 face / edge 集合;
区域边界;
修复后的 patch;
保留区域;
旧对象到新对象的匹配信息;
允许的边界缝合容差;
回滚快照。
输出也不应该只有一个新 shape,而应该包含:
新的 root shape;
被替换的旧 face / edge;
生成的新 face / edge;
保留下来的对象映射;
丢失或无法映射的选择;
局部和全局验证报告;
是否建议接受;
失败原因。
如果没有这些信息,上层很难同步:
模型树;
显示节点;
边界条件;
材料属性;
命名选择;
undo / redo;
后续网格区域。
所以区域化修复的关键接口不应该只是:
TopoDS_Shape repairFaces(selectedFaces);
更接近下面这种语义:
输入一个带边界和保护信息的 region;
输出一个可提交、可回滚、带映射和验证报告的 region transaction。
具体类型设计可以有很多种,但事务语义不能省略。
否则局部修复很容易变成:局部算出来了,但没法安全放回完整模型。
预览不只是把选中面染色
区域化修复非常适合做预览,因为影响范围相对明确。
但预览不应该只停留在“把用户选中的面染色”。
至少可以分成几个层次。
第一层是选择预览。
它显示用户当前选择的 seed,只能回答“用户选了什么”,不能回答“系统准备改什么”。
第二层是影响范围预览。
它应该高亮问题种子、核心修改区、上下文邻域、保护区和预计边界。
这一步能让用户发现区域是否扩得过大,或者是否碰到了不应该修改的面。
第三层是几何结果预览。
对补面、压印、自交修复和局部重建等高风险操作,只做高亮还不够。
系统应该尽量生成一份临时几何结果,并展示:
新增面;
删除面;
替换面;
新的边界;
仍未解决的自由边;
可能发生的内部拓扑变化。
更理想的做法是,最终提交直接复用已经验收过的 preview 结果,或者至少保证 preview 和最终执行来自同一套可追踪的数据。
否则用户看到的预览和确认后的真实结果不一致,交互式修复就会失去可信度。
验证要同时看局部和全局
区域化修复不能只验证局部 patch,也不能只验证整个 root shape。
至少需要三层验收。
第一层是目标问题验收。
先检查这次修复针对的问题是否改善:
free edge 是否减少;
gap region 是否消失;
non-manifold 是否减少;
self-intersection 是否消失;
共形风险是否下降;
小面或小边是否按预期处理。
第二层是区域边界验收。
再检查局部结果和保留区域之间的接口:
是否产生新的 open boundary;
边界 edge 是否匹配;
是否出现 T-junction;
方向和参数范围是否正常;
局部 Sewing 是否误合并;
保护区是否保持不变。
第三层是全局模型验收。
最后检查新的 root shape:
shape validity;
solid / shell 数量;
整体 free edge 和 non-manifold 数量;
shape 类型是否符合预期;
是否引入其他区域的新问题;
后续网格试剖是否通过。
局部验收通过、全局验收失败时,结果仍然不能提交。
例如一个 patch 在局部看起来闭合,但回装后导致 solid 被拆成多个 shell,这就属于局部正确、全局不可接受。
这也是区域化修复比普通局部算法更麻烦的地方:它既要证明局部问题改善,也要证明整体模型没有退化。
回退阶梯比反复硬修更重要
区域化修复不应该只有“成功”和“失败”两个出口。
更稳妥的是一个逐级回退过程:
当前核心区 + 一圈邻域,尝试低风险策略;
在预算内扩大一圈邻域,仍保持可控策略;
保持区域不变,切换到更强但可预览的策略;
转入全局专项修复,但明确提示影响范围扩大;
只输出问题报告,交给用户重新选择或人工处理。
每一级都应该重新验证,不能因为前一级失败就自动增大容差并一直重试。
有些情况应该立即停止,例如:
区域触碰保护对象;
范围超过预算;
输出 shape 无效;
目标问题没有改善;
shape 类型发生非预期变化;
修改跨越不同零件语义;
无法建立稳定的替换边界。
放弃自动修复也是区域策略的一部分。
如果系统不能证明局部修改比原模型更好,保留问题报告通常比生成一个不可解释的新 shape 更安全。
哪些问题不适合强行区域化
区域化修复不是所有问题的最佳答案。
有些问题本身就是全局性的,例如:
整个 shell 的面方向混乱;
大量 face 的容差体系不一致;
整个模型由离散碎片组成;
多个零件需要统一建立共形关系;
整体拓扑已经严重损坏;
模型需要完整重建 solid;
全局简化或尺度归一化。
还有些模型问题虽然局部可见,但修复结果会影响整个拓扑结构。例如一条关键分割边被替换后,可能改变大量下游 face 的归属。
遇到这些情况,更合理的做法可能是:
全局专项修复;
离散模型法或 Wrap;
重新导入;
重建 shell / solid;
由用户明确确认的大范围操作。
区域化的价值不是把所有全局算法都局部化,而是先问一句:
这个问题真的需要改动整个模型吗?
如果答案是需要,就应该明确进入全局路径,而不是维持一个虚假的“局部”标签。
性能上应该怎么看
区域化修复通常有机会减少重型几何算法的输入规模,但不能简单等同于“一定更快”。
它减少的主要是:
无关 face / edge 的遍历;
不必要的相交候选;
全局 Sewing 和重建范围;
修复后全量映射的压力;
失败时重复计算整个模型的成本。
它也会增加新的开销:
问题聚类;
邻域扩展;
区域边界提取;
局部 patch 回装;
新旧拓扑映射;
局部和全局双重验证。
对于问题很小、模型很大的场景,这些额外开销通常是值得的。
但如果问题已经遍布整个模型,region 数量很多,或者每个 region 最终都扩展到大范围,逐区域处理可能产生大量重复工作。这时应该合并重叠区域,或者直接切换到全局策略。
所以区域化修复的性能评价,不能只看“局部算法跑了多久”。
更有意义的问题是:
在不扩大误修改范围的前提下,
区域化流程是否提高了修复成功率和整体处理效率?
如果一个局部流程多花了一点区域构造和验证成本,但避免了全局误修,或者让用户明确看到了影响范围,那么这部分成本就是有意义的。
一个保守的落地顺序
如果要把区域化修复真正做进一个工程模块里,我不会一开始覆盖所有修复类型。
更稳妥的方式,是先从面缝隙、自由边、局部共形风险这类问题开始。
原因是这些问题往往有相对明确的问题区域,也比较容易构造 seed、core 和 context。
一个保守流程可以是:
从检测缓存或用户选择得到问题 seed;
收集 owner face 和一圈相邻 face;
检查区域是否跨 part、是否碰到保护面、是否超过预算;
生成 Region Preview,高亮 seed、core 和 context;
运行低风险局部策略;
构建局部结果并尝试回装 root shape;
比较目标问题、区域边界和全局有效性;
通过后提交局部替换事务;
不通过则完整回滚;
根据原因选择扩大一圈、切换策略、全局回退或停止。
第一版不急着做:
任意复杂自交的自动局部重建;
跨多个零件的 tool-target 合并;
没有真实几何预览的高风险压印;
无限邻域扩张;
无法映射边界条件时的静默提交;
把所有失败都自动转成大容差全局修复。
这样的第一版覆盖面有限,但验证路径清楚,更容易用真实模型建立信心。
小结
区域化模型修复的出发点很简单:
问题发生在哪里,修改就尽量限制在哪里。
但真正落地以后,它不是一个简单的局部 API,而是一套完整流程:
问题种子负责说明为什么修;
核心区负责限制允许修改的对象;
上下文区负责给算法提供必要信息;
保护区负责守住业务语义;
区域边界负责把局部结果接回原模型;
预览负责让影响范围可见;
验证负责判断结果是否真的改善;
事务负责让提交、撤销和映射保持一致;
回退阶梯负责在局部策略不适用时安全退出。
它的目标也不是彻底消灭全局修复。
更合理的关系是:
能明确定位、能稳定回装的问题,优先局部处理;
局部上下文不足时,在预算内扩大区域;
问题本身具有全局性时,明确进入全局专项修复;
无法证明结果改善时,停止并保留原模型。
对于复杂 CAD/CAE 模型来说,减少误修改往往和提高修复成功率同样重要。
区域化修复真正想建立的,不只是“少算几张面”,而是一条更清楚的责任边界:系统知道自己为什么修改这个区域,也知道哪些区域不应该被碰。
当这种边界能够通过预览、验证、回滚和结果报告被完整表达以后,模型修复才更接近一个可控的工程操作,而不是对整个 shape 做一次不可解释的全局尝试。