Technical note

从卡死到可取消:一次 CAD/CAE 模型导入管线的工程复盘

记录一次 CAD/CAE 模型导入模块的工程调整:为什么导入不只是 reader,为什么需要 poly-only 快速通道、轻量修复、软进度和可中断设计。


背景:导入模块真正难的不是读文件

CAD/CAE 软件里的模型导入,表面上看是一个文件解析问题。

用户选择 STEP、IGES、STL、OBJ、glTF 或 FBX 文件,系统调用对应 reader,得到一个几何对象,然后显示到视图里。这个过程听起来很直接,但真正做起来以后会发现,导入模块的难点并不只是“能不能把文件读进来”。

我这次遇到的问题主要有几类。

第一,大模型导入耗时很长,用户点击取消以后,程序仍然长时间卡在底层调用里,看起来像“取消无效”。

第二,不同格式的数据语义差异很大。STEP、IGES 更接近完整的 CAD BRep;STL、OBJ、FBX 很多时候只是 polygonal mesh。如果把所有格式都按同一套曲面模型修复逻辑处理,很容易又慢又危险。

第三,导入阶段不应该承担完整几何修复模块的职责。为了“修得更干净”而默认执行重修复,可能会让导入过程变成不可控的黑箱,也可能改变原始拓扑,影响后续选择、属性继承、网格和边界条件映射。

第四,导入不是孤立模块。读入的几何最终还要进入项目文档、拓扑映射、属性系统和显示系统。导入阶段如果输出不稳定,后面所有链路都会被影响。

所以这次调整的目标,不是简单补几个格式 reader,而是把导入整理成一条更稳定的工程管线:

读取可控;
进度可见;
取消尽量有效;
修复保持克制;
不同格式走不同策略;
最终稳定进入后续系统。

导入模块的边界

这次复盘里,我对导入模块的职责有一个更明确的判断:

导入模块负责把外部模型稳定带入系统,
不负责把所有坏模型默认修成完美模型。

这句话看起来简单,但对实现方式影响很大。

如果导入阶段什么都想做,问题会越来越复杂:读文件、修几何、修拓扑、保属性、建显示、写数据库、做进度、支持取消、处理异常,最后每一种格式都会变成一套特殊流程。

更稳妥的方式是把导入的边界收住。

导入阶段应该保证:

  • 文件能被尽量稳定地读取;
  • 输出几何不能为空;
  • 不做高风险的过度修复;
  • 保留尽可能多的原始拓扑语义;
  • 长耗时阶段有进度反馈;
  • 用户取消后能在可控边界尽快返回;
  • 后续模块可以继续接管拓扑展开、属性映射和显示构建。

真正复杂的几何修复,比如全局 Sewing、ShapeFix、面线修复、孔洞修复、法向统一、自交处理,更适合作为用户显式触发的修复功能,而不是导入时默认执行。

不同格式不能走同一套修复逻辑

这次问题里,一个比较重要的转折点是:我意识到不能把 STEP/IGES 和 STL/OBJ/FBX 当成同一类模型处理。

STEP 和 IGES 通常承载的是 CAD BRep 数据,里面有曲面、边界、参数曲线、拓扑关系。对这类模型,适当做一些轻量检查和受控修复是有意义的。

但 STL、OBJ、FBX 很多时候只是三角面片或多边形网格。它们并不一定具备完整的 3D curve、2D pcurve、曲面参数域和 CAD 拓扑语义。

如果对 polygonal mesh 强行执行曲面模型修复逻辑,就会出现几个问题:

  • SameParameter 可能没有实际意义;
  • 有效性检查在大面数模型上可能非常慢;
  • Sewing 可能改变原始网格拓扑;
  • 某些底层检查或修复调用不容易中断;
  • 导入进度可能长时间停在某个百分比,看起来像程序卡死。

所以后来导入流程里必须先判断模型类型。对于明显的 poly-only 模型,应该走快速通道,而不是进入 BRep 曲面修复流程。

这里的 poly-only 可以粗略理解为:模型主要由离散面片构成,缺少完整曲线和参数域信息,更接近显示网格,而不是严格 CAD 曲面模型。

对这类数据,导入阶段最重要的是保证位置、面片、法向、层级和显示可用,而不是强行把它恢复成高质量参数化曲面。

STEP / IGES:可以轻量检查,但不要默认强修复

STEP 和 IGES 是 CAD/CAE 场景里最常见的交换格式。它们确实比纯网格格式更适合进入 BRep 检查和轻量修复流程。

以 STEP 为例,读取过程大致可以理解为三个阶段:

ReadFile:读取和解析文件结构;
Transfer:把文件实体转换成 OCCT BRep;
OneShape:取出聚合后的 shape。

这里需要注意的是,ReadFile 成功并不代表已经得到了可用几何。真正耗时、也更容易卡住的,往往是 transfer 和后续检查修复阶段。

IGES 的接口流程和 STEP 接近,但实际工程里,IGES 更容易遇到自由边、面方向异常、曲面参数不一致等问题。因此 IGES 导入时保留轻量修复是合理的。

但我后来的原则是:只做受保护、可失败、可跳过的修复。

也就是说:

SameParameter 可以尝试;
快速有效性检查可以尝试;
轻度 Sewing 可以尝试;
但任何一步失败,都不应该直接导致导入整体失败。

导入阶段的目标是“尽可能稳定地读入”,不是“把所有几何问题一次性修完”。

如果模型本身质量较差,导入后应该允许用户进入专门的几何修复流程,而不是在导入时默认执行强修复。这样既能控制耗时,也能避免在用户不知情的情况下改变拓扑结构。

STL / OBJ / FBX:poly-only 模型应该走快速通道

STL、OBJ、FBX 这类格式在很多场景下更接近显示资产,而不是 CAD 拓扑资产。

STL 通常只有三角面片。

OBJ 可能有多边形、法向、纹理坐标,但并不具备 CAD 意义上的边界曲线和曲面参数域。

FBX 常见于 DCC 或渲染资产,里面可能有复杂层级、材质、变换和 mesh,但它也不是 OCCT 原生的 CAD 交换格式。

所以对这些格式,我更倾向于把它们识别为 polygonal mesh,并进入快速通道:

跳过 SameParameter;
跳过重型 BRepCheck;
不默认 Sewing;
保留取消检查;
保留软进度;
尽快进入后续显示和管理流程。

这个策略看起来像是“少做了修复”,但对这类模型反而更合理。

因为它们的主要价值不是恢复精确 CAD 曲面,而是把外部网格稳定带进系统,保证后续能显示、选择、管理和继续处理。

如果导入阶段强行把这些 mesh 当作 BRep 曲面模型修复,不但可能没有收益,还可能带来明显副作用。

轻量修复:只做必要的,不做危险的

这次导入模块调整里,我认为最关键的设计变化是:不再把“导入”和“强修复”混在一起。

轻量修复的流程可以概括成三步。

第一步,先判断模型是否是 poly-only 或巨大面数模型。

如果是,就直接跳过重修复。对于这种模型,强行做曲面检查和 Sewing 往往不是优化,而是在制造风险。

第二步,对非 poly-only 的曲面模型,尝试逐 face 执行必要的参数一致性处理。

这里要注意两个点:一是每个长循环都应该检查取消;二是高风险 OCCT 调用周围要做好异常保护,避免某个脏数据模型直接把导入流程打崩。

第三步,执行一次快速有效性检查。如果已经有效,就直接返回。如果无效,可以尝试一次轻度 Sewing。Sewing 成功才替换 shape;失败就记录 warning,保留原 shape 继续导入。

这套策略背后的原则是:

修复成功,是增强;
修复失败,不应该默认变成导入失败。

导入模块应该保持克制。真正的强修复,应该留给用户显式触发的几何修复模块。

可中断导入:不是所有黑箱调用都能立即停

导入体验里,取消是一个很容易被低估的问题。

用户点击取消后,如果程序仍然长时间没有响应,用户感受到的就是“软件卡死了”。但从实现角度看,一些底层库调用并不一定支持真正的中途打断。

比如文件 reader、transfer、几何检查、Sewing、FBX scene import 这类调用,很多时候内部就是一个黑箱。我们不一定能在它执行到一半时安全终止。

所以这次我对“可中断”的理解更实际一些:

不承诺所有底层调用都能立刻停止;
但必须在所有可控边界尽快检查取消。

取消检查应该覆盖几类位置:

  • 重步骤之前,例如读取、transfer、repair、写入后续系统之前;
  • 重步骤之后,例如 reader 返回、transfer 完成、修复完成之后;
  • 长循环顶部,例如多 root transfer、逐 face 处理、逐 mesh 处理;
  • 黑箱调用边界,例如 OCCT reader、几何检查、FBX import 前后。

这样做不能让所有操作都瞬间停止,但可以避免“用户点了取消,系统还继续跑完整个流程”的情况。

对工业软件来说,这已经是一个很重要的体验提升。

软进度:解决“进度条不动但程序还在跑”

导入过程中还有一个典型问题:程序其实还在执行,但进度条长时间不动。

这在大模型导入时很常见。比如 STEP transfer、IGES transfer、BRep 检查、Sewing、FBX import 这些阶段,内部可能没有稳定的细粒度进度回调。

如果进度条一直停在某个百分比,用户很难判断程序是在正常计算,还是已经卡死。

所以这次增加了软进度。

软进度不是伪造真实进度,而是在已知阶段范围内,根据当前阶段的工作量和时间节流,给用户一个持续反馈。

例如:

多 root transfer:
    可以按 root 数推进;

逐 face 处理:
    可以按 face 数推进;

逐 mesh 处理:
    可以按 mesh 数或三角数推进;

黑箱调用:
    至少在阶段开始和结束时打点。

这里有两个细节很重要。

第一,软进度应该并入当前导入任务,而不是另起一个和任务无关的线程去打印。否则前端进度系统可能识别不到对应任务,或者出现进度来源混乱。

第二,软进度要节流。不能为了“看起来在动”而频繁刷日志,否则反而会影响性能,也会让真正有价值的日志被淹没。

软进度解决的不是算法复杂度,而是用户体验问题。它让用户知道:程序不是卡死,而是在某个已知阶段继续执行。

导入阶段不要随意改变拓扑

模型导入后,通常还会进入后续的拓扑展开、属性映射、显示构建和网格流程。

这意味着导入阶段不能只考虑“shape 是否变得更干净”,还要考虑拓扑是否被不必要地改变。

如果导入时默认执行过强的 Sewing 或 ShapeFix,就可能出现:

  • face 被合并;
  • edge 被重建;
  • tag 或映射关系不稳定;
  • 用户后续选择的面、边和原始模型对不上;
  • 边界条件、材料、网格局部设置继承出现问题。

所以我后来对导入修复的态度更保守:

能轻修就轻修;
不能确定收益时就不修;
强修复必须让用户显式触发。

这不是“不重视模型质量”,而是把不同模块的职责分清楚。

导入模块负责稳定进入系统;修复模块负责在用户明确需要时处理几何质量问题。

当前版本还可以继续优化什么

这次调整后,导入流程的边界更清楚了,但还有不少可以继续演进的地方。

第一,可以把巨大面数阈值、修复容差、软进度间隔等参数做成运行时配置。不同机器、不同模型规模、不同格式,适合的阈值不一定相同。

第二,可以进一步统计各阶段耗时,例如:

ReadFile
Transfer
OneShape
SameParameter
Analyzer
Sewing
TopologyMap
显示构建

这样后续优化时就不会只凭感觉判断。

第三,XDE 类格式可以逐步保留更多语义信息,比如名称、颜色、装配层级和材质。当前主线优先保证导入稳定,后续可以再增强属性映射。

第四,mesh-only 模型可以考虑建立更轻的显示和选择路径。OBJ、STL、FBX 不一定都需要完整模拟 CAD BRep 行为,后续可以按显示和交互需求建立更合适的 mesh topology。

第五,强修复能力应该继续往专门的几何修复模块里收敛。导入阶段只做低风险处理,复杂修复交给显式命令。

小结

这次导入模块调整后,我对“模型导入”这件事的理解更清楚了一些。

导入不是 reader 列表,也不是把所有格式都硬塞进同一个修复流程。它更像一条工程管线,需要在不同格式、不同模型质量、不同数据规模之间做取舍。

这次形成的几个判断是:

1. STEP / IGES 可以轻量检查,但不应该默认强修复;
2. STL / OBJ / FBX 这类 poly-only 模型应该走快速通道;
3. 导入阶段不应该随意改变拓扑;
4. 可取消不等于所有底层调用都能立刻停,但边界必须检查;
5. 软进度不是伪造结果,而是减少用户面对黑箱调用时的不确定感;
6. 导入模块的目标是稳定进入系统,而不是修复所有坏模型。

这些策略并不激进,但更符合工程实际。

对于 CAD/CAE 软件来说,导入阶段最重要的不是“做最多的事”,而是在复杂模型和脏数据面前始终保持可控:不崩溃、不卡死、能取消、能反馈,并且不给后续拓扑、属性和网格流程埋下不必要的隐患。