Files
cadSet/SimpleCADAPI/skills/simplecadapi/references/inverse_engineer/brep-reverse-engineering.md

23 KiB
Raw Permalink Blame History

STEP BREP 逆向工程方法

本文记录一种面向精确 BREP 的 STEP 逆向工作流。目标不是做出“看起来相似”的模型,而是恢复一个满足以下硬约束的可编辑参数模型:

  1. 几何点集一致;
  2. BREP 拓扑一致;
  3. 解析曲面的参数一致;
  4. 边界曲线和关键参数范围一致;
  5. 模型能够稳定重建和回放。

当多种特征树都满足这些硬约束时,再用工程师的建模习惯筛选:优先对称、整数或标准尺寸、少量草图、稳定基准、清晰特征依赖和容易修改的参数。

1. 先区分五种“一致”

STEP 逆向中最容易犯的错误,是把不同层次的一致混为一谈。

1.1 外观一致

截图、网格或渲染结果相似。它只能用于快速发现大方向错误,不能用于验收。

截图看不出:

  • 两个相邻面是否已合并;
  • 一个圆柱面是整圆还是两个半圆;
  • 隐藏孔、沉孔深度和内部台阶;
  • 半径的微小差异;
  • BREP 邻接关系;
  • 曲面的参数原点和方向。

1.2 数值摘要一致

包围盒、体积、表面积、质心一致。这比外观更强,但仍不充分。不同实体可能碰巧拥有相同的体积和包围盒。

用途:

  • 快速检查单位和坐标系;
  • 定位哪个特征导致体积误差;
  • 验证孔、槽、台阶的总体材料增减;
  • 在迭代过程中排除明显错误。

1.3 几何点集一致

两个实体占据完全相同的空间。对实体可用双向布尔差集验证:

A - B 的体积 = 0
B - A 的体积 = 0

这是几何一致的硬检查,但它不保证拓扑一致。例如一个完整圆柱面和两个半圆柱面可以表示相同点集。

1.4 BREP 拓扑一致

面、边、顶点数量相同,并且面—边—顶点邻接图同构。

仅比较数量还不够。两个模型都可能有 20 个面,但连接方式不同。可靠做法是给图节点添加几何标签,再检查图同构:

  • 面标签:类型、面积、几何中心、半径、轴线、球心等;
  • 边标签:类型、长度、几何中心、半径、轴线等;
  • 顶点标签:三维坐标;
  • 图边:Face—Edge 和 Edge—Vertex 关联。

1.5 参数化表示一致

同一个几何载体仍可能具有不同参数表示:

  • 圆柱轴方向相反;
  • 平面原点不同;
  • 圆弧起始角不同;
  • UV 范围平移或反向;
  • 一条整圆边与两条半圆边;
  • 相同孔通过不同特征顺序产生不同的曲面参数原点。

有些参数差异只是非唯一表示,不应误判为几何差异;有些则是建模历史的有效证据。必须结合设计语义判断。

2. 检查手段速查表

检查手段 最适合回答的问题 能提供的直觉 不能单独证明什么
STEP 实体类型计数 文件主要由什么几何组成 是机械解析体、自由曲面还是混合模型 导入后的唯一拓扑数量
BREP 有效性 文件能否作为可靠实体继续运算 是否存在开壳、坏边、容差问题 几何是否符合设计意图
唯一 Face/Edge/Vertex 数量 拓扑复杂度和分段程度 圆是否被拆弧、面是否被切分 邻接关系是否一致
拓扑出现次数 面如何重复引用边和顶点 是否像闭合流形、是否有非流形连接 两模型是否同构
包围盒 单位、整体尺寸和主方向 哪个方向可能是厚度或长度 内部孔槽和局部形状
体积/面积/质心 材料增减是否正确 漏掉的是大特征还是表面细节 几何点集和拓扑一致
解析面类型统计 特征家族是否正确 孔、倒角、圆角、自由曲面来源 每个面的具体位置和连接
面参数与包围盒 单个特征的尺寸和位置 半径、轴线、台阶、角度、深度 建模历史唯一解
边类型与端点 草图分段和切点 整圆/半圆、45 度线、圆角切线 隐藏面的完整形状
面-边-顶点邻接图 BREP 连接结构是否一致 哪个局部特征造成分段差异 几何点集必然一致
双向布尔差集 两实体是否占据相同空间 几何已经完成,可转向拓扑检查 面分段和参数化一致
多视图截图 大方向和明显特征是否正确 最快发现轴向、孔位、台阶错误 精确尺寸、内部结构和拓扑
物理截面 隐藏结构和不同深度轮廓 沉孔、槽、壁厚、局部层级 未采样位置必然一致
截面 XOR 候选差异出现在哪个区域 直接定位下一步应修改的特征 连续空间上的严格零差异
UV/曲线参数范围 参数历史和特征顺序 工具从哪个基准面开始、何时被后续特征裁剪 非唯一坐标架必然相同
模型图回放 建模过程能否稳定复现 是否依赖交互状态或临时对象 回放结果与目标自动一致

推荐检查顺序是从便宜到昂贵:

有效性和摘要
-> 解析几何清点
-> 多视图和少量关键截面
-> 面边参数与邻接
-> 候选构建
-> 双向差集
-> 图同构
-> 参数历史
-> 回放

不要一开始就对所有点做高密度分类,也不要一开始就逐个比较 STEP 文本实体。前者成本高且信息增量有限,后者会被导出器编号和非唯一参数化干扰。

3. 推荐的分阶段工作流

3.1 阶段 A:建立不可变基准

在写建模代码前,先从原始 STEP 导出一份机器可读报告。可以先用文本清点快速了解文件组成:

rg -o '^[[:space:]]*#[0-9]+[[:space:]]*=[[:space:]]*[A-Z0-9_]+' model.step \
  | sed -E 's/.*=[[:space:]]*//' \
  | sort \
  | uniq -c \
  | sort -nr

这能快速发现 ADVANCED_FACE、PLANE、CYLINDRICAL_SURFACE、B_SPLINE_SURFACE_WITH_KNOTS 等实体,但它只是 STEP 表示层清点。EDGE_CURVE 数量不一定等于内核导入后的唯一 Edge 数量;退化边、缝边和表示方式都会造成差异。最终基准必须来自导入后的 BREP。

机器可读报告至少包括:

  • STEP 是否能读取;
  • 转移根数量;
  • BREP 是否有效;
  • Solid、Shell、Face、Edge、Vertex 数量;
  • 唯一拓扑数量和拓扑出现次数;
  • 包围盒、体积、表面积、质心;
  • 面类型统计;
  • 边类型统计;
  • 每个面的面积、UV 范围、解析曲面参数和邻接边;
  • 每条边的类型、参数范围和邻接面。

唯一数量和出现次数必须分开:

unique_edges       BREP 中不同的边对象数量
edge_occurrences   遍历所有面时边被引用的总次数

在闭合流形实体中,一条普通边通常被两个面引用,因此出现次数通常接近唯一边数的两倍。

正式实现位于:

src/simplecadapi/inverse_engineer/brep/inspect.py

Python API:

from simplecadapi.inverse_engineer import brep

report = brep.inspect_step(path="model.step")
report.write_json("model-report.json")

CLI:

uv run simplecad-brep inspect model.step -o model-report.json

基础属性代码片段

from OCP.BRepCheck import BRepCheck_Analyzer
from OCP.BRepGProp import BRepGProp
from OCP.BRepBndLib import BRepBndLib
from OCP.Bnd import Bnd_Box
from OCP.GProp import GProp_GProps

assert BRepCheck_Analyzer(shape).IsValid()

bbox = Bnd_Box()
BRepBndLib.AddOptimal_s(shape, bbox)
print("bbox", bbox.Get())

volume_props = GProp_GProps()
BRepGProp.VolumeProperties_s(shape, volume_props)
print("volume", volume_props.Mass())
print("center", volume_props.CentreOfMass().Coord())

area_props = GProp_GProps()
BRepGProp.SurfaceProperties_s(shape, area_props)
print("area", area_props.Mass())

3.2 阶段 B:恢复坐标语义

不要根据 X/Y/Z 名称直接假设左右、前后和高度。

应综合使用:

  • 包围盒三个跨度;
  • 主成分分析;
  • 平面和圆柱轴线分布;
  • 对称平面拟合;
  • 正交视图和典型截面;
  • 机械功能,例如孔轴通常对应装配或加工方向。

坐标语义判断错误会导致整个特征解释反转。比如一个方向上的 U 形截面可能是左右对称结构,也可能被错误解释为前后凹槽。

3.3 阶段 C:做解析几何清点

解析面类型是最有价值的“建模指纹”之一。

几何类型 常见来源 可恢复的信息
Plane 拉伸端面、切割平面、倒角 基准方向、台阶位置、厚度、角度
Cylinder 孔、轴、拉伸圆弧、直边圆角 半径、轴线、深度、圆弧分段
Sphere 三条圆角边交汇、球切割 圆角半径、特征交汇方式
Torus 圆弧轮廓的边圆角、环形过渡 主半径、次半径、圆角中心
Cone 锥孔、沉头、拔模 锥角、轴线、顶点
BSpline 放样、填充、自由曲面 次数、节点、控制点、连续性

例如:

4 个 R1 Sphere
4 个 (R2, R1) Torus
14 个 R1 Cylinder

通常不是手工拼出的 22 个面,而是一次连续局部 R1 圆角的结果。解析面组合比截图更能揭示特征操作。

读取解析曲面参数

from OCP.BRepAdaptor import BRepAdaptor_Surface

surface = BRepAdaptor_Surface(face, True)
kind = surface.GetType()

cylinder = surface.Cylinder()
print("radius", cylinder.Radius())
print("axis", cylinder.Axis().Direction().Coord())
print("origin", cylinder.Location().Coord())

sphere = surface.Sphere()
print("radius", sphere.Radius())
print("center", sphere.Location().Coord())

torus = surface.Torus()
print("major", torus.MajorRadius())
print("minor", torus.MinorRadius())

3.4 阶段 D:从平面和边恢复尺寸关系

不要只读包围盒。每个解析面的平面方程、轴线和边界端点能够恢复:

  • 台阶高度;
  • 壁厚;
  • 孔深;
  • 孔距;
  • 对称位置;
  • 45 度倒角;
  • 圆角切点;
  • 草图中的直线方程。

例如一组斜面端点满足:

x + |z| = 1.25

这比“看起来大约 45 度”更强,说明草图很可能使用了对称 45 度约束。

读取边端点和类型

from OCP.BRepAdaptor import BRepAdaptor_Curve

curve = BRepAdaptor_Curve(edge)
first = curve.FirstParameter()
last = curve.LastParameter()
p0 = curve.Value(first)
p1 = curve.Value(last)

print("type", curve.GetType())
print("start", p0.Coord())
print("end", p1.Coord())

if "Circle" in str(curve.GetType()):
    circle = curve.Circle()
    print("radius", circle.Radius())
    print("center", circle.Location().Coord())

3.5 阶段 E:建立直观图像,但只让它承担合适的职责

推荐同时生成:

  1. 等轴视图;
  2. 三个正交视图;
  3. 关键高度或轴向位置的占用截面;
  4. 目标/候选叠加图;
  5. XOR 差异图。

多视图适合发现:

  • 坐标方向错误;
  • 漏掉大特征;
  • 孔位错误;
  • 台阶方向错误;
  • 圆角范围过大;
  • 草图轮廓错误。

截面图适合发现:

  • 内孔层级;
  • 沉孔深度;
  • 不同高度处的轮廓变化;
  • 隐藏槽;
  • 两实体的局部差异。

正式实现:

src/simplecadapi/inverse_engineer/brep/render.py
src/simplecadapi/inverse_engineer/brep/slices.py

渲染图片需要可选依赖:

uv sync --extra inverse-engineer

Python API:

from simplecadapi.inverse_engineer import brep

brep.render_step_views(step_path="candidate.step", output_path="candidate-views.png")
comparison = brep.compare_step_slices(
    target_path="target.step",
    candidate_path="candidate.step",
    output_path="slice-overlay.png",
)

CLI:

uv run simplecad-brep render candidate.step candidate-views.png
uv run simplecad-brep slices target.step candidate.step slice-overlay.png
# 默认自动检查联合包围盒的 X/Y/Z 中心截面;特征边界应显式追加:
uv run simplecad-brep slices target.step candidate.step feature-slices.png \
  --slice xz:-1.6 --slice xz:4.0 --slice xy:0

截面分类示例:

from OCP.BRepClass3d import BRepClass3d_SolidClassifier
from OCP.TopAbs import TopAbs_IN, TopAbs_ON
from OCP.gp import gp_Pnt

classifier = BRepClass3d_SolidClassifier()
classifier.Load(shape)
classifier.Perform(gp_Pnt(x, y, z), 1e-8)
inside = classifier.State() in (TopAbs_IN, TopAbs_ON)

默认截面只用于快速建立直觉。逆向时应进一步选择穿过特征边界、台阶高度、沉孔肩部、对称面和疑似差异区域的物理截面。

注意:逐点 BREP 分类较慢。截面图是诊断工具,应控制分辨率;几何硬验收应使用布尔差集,而不是靠提高图片分辨率。

3.6 阶段 F:提出最小特征假设

先写出一个人类可读的候选特征树,不要马上编码大量细节。

好的候选通常包含:

  • 一个主草图和一次拉伸;
  • 对称约束或镜像;
  • 少量孔、槽、台阶;
  • 局部圆角和倒角;
  • 清晰的加工顺序。

每个参数都应能由原始 BREP 的证据解释。例如:

R1      来自 14 个 R1 圆柱、4 个 R1 球和 4 个次半径 R1 的圆环
R3.25   来自沉孔圆柱
R1.7    来自贯穿孔圆柱
X=3.25  来自台阶平面
Z=±6    来自对称孔轴

避免在没有证据时使用:

  • 随机小偏移;
  • 大量补偿布尔体;
  • 任意样条;
  • 网格拟合;
  • 按最终面逐面拼壳。

3.7 阶段 G:按特征逐步落地

每一步都打印少量有意义的基准:

print(
    "ground opening fillet",
    body.get_volume(),
    len(body.get_faces()),
    len(body.get_edges()),
)

推荐顺序:

  1. 主轮廓;
  2. 主拉伸;
  3. 大型切除或并集;
  4. 台阶;
  5. 孔;
  6. 圆角和倒角,或根据几何指纹调整到正确历史位置;
  7. 输出 STEP;
  8. 生成报告和截图;
  9. 对比;
  10. 只修改造成当前差异的特征。

不要一次添加所有特征。否则拓扑不一致时无法知道是哪一步造成的。

4. 从差异反推建模历史

4.1 面数少但几何相同:检查草图分段

典型情况:

目标:每个孔壁由两个半圆柱面组成
候选:每个孔壁是一个完整圆柱面

二者几何点集相同,但拓扑不同。原因通常不是布尔算法随机拆面,而是草图边界不同:

完整圆边拉伸       -> 一个圆柱侧面
两条半圆弧闭合拉伸 -> 两个半圆柱侧面

因此,拓扑差异常常能反推草图实体数量。

4.2 面数多且出现额外平面:检查错误的布尔拆分

把一个圆柱工具拆成左右两个半圆实体分别切除,会得到两个半圆柱面,但也会引入径向分隔平面。

这是一条重要经验:

想得到分段侧面,应在草图边上分段,而不是把实体工具分成多个带内部平面的实体。

4.3 几何和拓扑相同但 UV 不同:检查特征顺序

典型沉孔:

方案 A:先沉孔,再从肩部开始切小孔
方案 B:先切贯穿小孔,再切大沉孔

两者最终几何可能相同,但小孔圆柱支撑面的参数原点不同。

如果目标小孔的圆柱原点位于入口平面,UV 还保留了被沉孔切掉的参数区间,这通常说明:

先贯穿孔
再沉孔

特征顺序不仅影响工程可读性,也会影响 STEP 中保留的曲面参数。

4.4 球面和圆环面组合:检查三维圆角交汇

二维草图圆角后拉伸,常产生圆柱面;对三维实体的多条相交边做圆角,会进一步产生球面和圆环面。

因此:

  • 只有圆柱圆角面:可能是二维圆角轮廓拉伸;
  • 同时出现球面和圆环面:更像三维成组选边圆角;
  • 球面半径和圆环次半径相同:通常直接给出圆角半径。

5. 严格验收方法

5.1 双向布尔差集

from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut
from OCP.BRepGProp import BRepGProp
from OCP.GProp import GProp_GProps


def cut_volume(a, b):
    operation = BRepAlgoAPI_Cut(a, b)
    operation.SetFuzzyValue(1e-9)
    operation.Build()
    props = GProp_GProps()
    BRepGProp.VolumeProperties_s(operation.Shape(), props)
    return props.Mass()

assert cut_volume(target, candidate) < 1e-9
assert cut_volume(candidate, target) < 1e-9

这证明实体点集相同,但仍需拓扑检查。

5.2 几何标记的邻接图同构

正式实现位于:

src/simplecadapi/inverse_engineer/brep/compare.py

Python API:

from simplecadapi.inverse_engineer import brep

comparison = brep.compare_steps(target_path="target.step", candidate_path="candidate.step")
assert comparison.hard_gate_passed

CLI:

uv run simplecad-brep compare target.step candidate.step -o comparison.json

报告应同时满足:

same_geometric_point_set = true
geometry_labelled_incidence_graph_isomorphic = true

规范化非唯一参数

比较几何载体时,不要直接比较平面原点或轴向符号。

平面可规范为:

单位法向 n
有符号距离 d = n dot p

轴线可规范为:

单位方向 d,统一符号
原点到世界原点的最近点 p - d(d dot p)

这样可以识别“同一个平面但原点不同”和“同一根轴但方向相反”。

5.3 参数化表示检查

完成几何和拓扑硬检查后,再检查:

  • UV 范围;
  • 圆柱、圆锥、圆环坐标架;
  • 圆弧参数范围;
  • B 样条次数、节点、重数、控制点和权重;
  • 边方向和面方向;
  • 曲面原点是否反映预期特征顺序。

不要把所有参数差异都当成错误。先问:

  1. 这是非唯一坐标架选择吗?
  2. 它是否改变几何点集?
  3. 它是否改变 BREP 邻接?
  4. 它是否揭示不同的草图分段或特征顺序?
  5. 用户要求的是精确 BREP,还是 STEP 文本字节一致?

STEP 实体编号和文本顺序通常不属于 BREP 本体,不应作为几何逆向的默认验收项。

5.4 回放验收

逆向模型不能只在当前 Python 进程中成功。应记录模型图,重新播放,再对回放结果执行相同检查。

import simplecadapi as scad

with scad.GraphSession(graph_id="reverse_part") as session:
    body = build_body()
    scad.capture_result(value=body)

payload = scad.export_model_json(session)
rebuilt = scad.replay_model_json(payload)[0]
scad.export_step(rebuilt, "replayed.step")

需要分别比较:

原始 vs 直接构建
原始 vs 回放构建
直接构建 vs 回放构建

6. 高效迭代策略

6.1 用差异类型决定下一步

当前差异 优先检查
包围盒错误 坐标系、单位、基准位置
体积差很大 漏掉主切除/并集、特征方向错误
体积接近但面积差大 圆角、倒角、孔壁或隐藏台阶
几何相同但面数少 草图边分段不足、同域面被合并
几何相同但面数多 多余切割平面、重复布尔、错误拆工具
Sphere/Torus 数量错误 圆角选边范围或圆角时序
Cylinder 数量错误 圆/圆弧草图分段、孔特征顺序
拓扑计数相同但图不同 面邻接、边界方向、错误局部连接
UV 原点或范围错误 特征创建顺序、工具起始平面

6.2 先匹配低维输入,再匹配高维结果

工程特征一般遵循:

点/尺寸 -> 草图边 -> 闭合轮廓 -> 面 -> 拉伸/旋转 -> 布尔 -> 圆角

当最终圆柱面分段不对时,优先检查草图圆弧数量,而不是在最终实体上补切割面。低维输入通常才是拓扑来源。

6.3 用解析面统计做中间目标

完整拓扑还没对齐时,解析面统计是非常有效的阶段目标。例如:

目标:Plane 16, Cylinder 22, Sphere 4, Torus 4

候选若为:

Plane 16, Cylinder 18, Sphere 4, Torus 4

说明主体、台阶和圆角网络很可能已正确,剩余问题集中在圆柱类特征,不应重做整个主体。

6.4 保存候选,但不要让试验污染最终入口

建议保留:

  • 每轮 STEP;
  • 每轮报告;
  • 每轮截图;
  • 差异日志;
  • 被否定的假设及原因。

最终应收敛为一个独立、可读、可回放的入口,不能依赖多个临时试验脚本互相导入。

7. 本次实践中走过的弯路

7.1 只根据截图猜主特征

问题:截图遮挡内部结构,容易把缺口、孔和圆角顺序解释错。

改进:截图只用于提出假设;立即用解析曲面、面边界和截面验证。

7.2 对所有边做圆角

问题:fillet(all_edges) 产生了过多球面,且破坏目标的平面和圆环统计。

改进:根据设计语义分组选边。先识别“左侧开口轮廓”这一语义组,再验证它生成的解析面组合。

7.3 用两个半圆实体切出分段孔

问题:虽然圆柱面数量增加到目标值,却额外产生了两个径向平面。

改进:用两条半圆弧组成一个闭合草图,再做一次拉伸切除。

7.4 把体积和面积一致当作完成

问题:完整圆柱孔和两个半圆柱孔具有相同几何、体积和面积,但拓扑不同。

改进:加入唯一拓扑数量、解析面统计和邻接图同构。

7.5 直接比较平面原点和轴向

问题:同一个平面可选择任意平面内原点,同一根轴可反向,导致大量假差异。

改进:比较规范化平面方程和规范化轴线,不比较任意坐标架选择。

7.6 截面采样过密

问题:逐点 BREP 分类成本高,六张高分辨率截面会超时。

改进:截面图保持适中分辨率用于直观诊断;精确几何一致由双向布尔差集负责。

7.7 先做沉孔再从肩部做小孔

问题:最终几何和拓扑正确,但小孔圆柱参数原点与目标不同。

改进:先从入口平面做贯穿孔,再做沉孔。这个顺序既符合常见加工意图,也复现目标 UV 历史。

8. 推荐的自动化产物

每个逆向任务建议固定生成:

reference_report.json
candidate_report.json
candidate.step
candidate_replayed.step
candidate.model.json
candidate_views.png
candidate_slice_overlay.png
candidate_brep_compare.json

最终验收至少包括:

BREP valid
双向差集为零
唯一 Face/Edge/Vertex 数量一致
解析面和边类型统计一致
几何标记邻接图同构
关键解析参数一致
模型图可重放
回放结果再次通过全部检查

9. 核心方法总结

最有效的逆向顺序不是“看图后直接建模”,而是:

建立精确基准
-> 恢复坐标语义
-> 清点解析几何
-> 从平面、轴线和边界恢复尺寸关系
-> 用多视图与截面建立直觉
-> 提出最小工程特征树
-> 逐特征构建和输出
-> 用差异类型指导下一轮
-> 双向差集验证几何
-> 几何标记图验证拓扑
-> UV/曲线参数验证特征历史
-> 模型图回放并重复全部验收

最重要的判断原则是:

几何和拓扑是硬约束;工程师先验只用于在满足硬约束的多种过程之间做选择。解析几何告诉我们“是什么”,拓扑分段告诉我们“草图如何画”,参数范围和坐标架经常告诉我们“特征按什么顺序做”。