znlgis 博客

GIS开发与技术分享

第18章:二次开发实战与综合案例

前 17 章把 OCCT 的“技术栈”一一拆解。本章以一个综合案例把它们串起来:实现一个简化版的“参数化机械零件查看器”,覆盖建模、可视化、数据交换、OCAF、Draw 测试与部署。读者可以把它当作动手项目,逐步扩展为自己的 CAD 应用。

1. 案例需求

  • 用 OCCT 构造一个参数化“法兰盘”模型;
  • 提供 Qt 视图,可旋转/缩放,支持面拾取;
  • 参数(外径、内径、孔数、孔径、厚度)可在 UI 修改;
  • 模型可导出 STEP、glTF、STL;
  • 用 OCAF 维护参数与撤销/重做;
  • 用 Draw 脚本编写自动化回归测试。

2. 工程结构

flange-app/
  CMakeLists.txt
  src/
    main.cpp
    MainWindow.cpp/.hpp
    OcctView.cpp/.hpp
    FlangeBuilder.cpp/.hpp
    FlangeDocument.cpp/.hpp
  tests/
    flange.tcl
  resources/

依赖:Qt6、OpenCASCADE 7.8+、CMake 3.22+。

3. 参数化建模(FlangeBuilder)

struct FlangeParams {
    double outerDiameter = 120.0;
    double innerDiameter = 40.0;
    double thickness = 20.0;
    int    boltCount = 6;
    double boltDiameter = 10.0;
    double boltCircle = 80.0;
};

TopoDS_Shape FlangeBuilder::Build(const FlangeParams& p) {
    gp_Ax2 ax(gp_Pnt(0,0,0), gp_Dir(0,0,1));
    TopoDS_Shape body = BRepPrimAPI_MakeCylinder(ax, p.outerDiameter/2, p.thickness).Shape();
    TopoDS_Shape hole = BRepPrimAPI_MakeCylinder(ax, p.innerDiameter/2, p.thickness + 2).Shape();
    TopoDS_Shape r1 = BRepAlgoAPI_Cut(body, hole);

    BRep_Builder bb; TopoDS_Compound bolts; bb.MakeCompound(bolts);
    for (int i = 0; i < p.boltCount; ++i) {
        double a = 2 * M_PI * i / p.boltCount;
        gp_Ax2 axb(gp_Pnt(p.boltCircle/2 * std::cos(a),
                          p.boltCircle/2 * std::sin(a), -1),
                   gp_Dir(0,0,1));
        bb.Add(bolts, BRepPrimAPI_MakeCylinder(axb, p.boltDiameter/2, p.thickness + 2).Shape());
    }
    TopoDS_Shape r2 = BRepAlgoAPI_Cut(r1, bolts);

    BRepFilletAPI_MakeFillet f(r2);
    for (TopExp_Explorer ex(r2, TopAbs_EDGE); ex.More(); ex.Next()) {
        gp_Pnt mid; double u1, u2;
        BRepAdaptor_Curve c(TopoDS::Edge(ex.Current()));
        mid = c.Value(0.5*(c.FirstParameter()+c.LastParameter()));
        if (std::abs(mid.Z() - p.thickness) < 1e-3 || std::abs(mid.Z()) < 1e-3) {
            f.Add(1.0, TopoDS::Edge(ex.Current()));
        }
    }
    f.Build();
    return f.IsDone() ? f.Shape() : r2;
}

要点:

  • 解析体优先(MakeCylinder);
  • 用 Compound 收集所有螺栓孔,一次 Cut
  • 圆角通过几何位置筛选边;
  • IsDone() 检查后退回上一步。

4. OCAF 文档(FlangeDocument)

class FlangeDocument {
public:
    void New();                // 创建空文档
    void SetParams(const FlangeParams&);
    FlangeParams GetParams() const;
    void Recompute();          // 重算并写入 NamedShape
    void Save(const TCollection_AsciiString& path);
    void Open(const TCollection_AsciiString& path);
    void Undo(); void Redo();

    Handle(TDocStd_Document) Doc() const { return myDoc; }
    TDF_Label ResultLabel() const { return myDoc->Main().FindChild(1, true); }
private:
    Handle(TDocStd_Document) myDoc;
};

实现要点:

  • TDataStd_Real 保存每个参数到独立子 label;
  • Recompute 中调用 BRepAlgoAPI 等并 TNaming_Builder::Generated(shape) 写结果;
  • NewCommand / CommitCommand 包裹每次修改;
  • BinXCAF 格式保存,便于以后扩展 XCAF 颜色/层。

5. 视图(OcctView)

继承 QWidget

class OcctView : public QWidget {
public:
    OcctView(const Handle(AIS_InteractiveContext)& ctx, QWidget* parent = nullptr);
protected:
    QPaintEngine* paintEngine() const override { return nullptr; }
    void paintEvent(QPaintEvent*) override { myView->Redraw(); }
    void resizeEvent(QResizeEvent*) override { myView->MustBeResized(); }
    void mousePressEvent(QMouseEvent*) override;
    // ... 鼠标事件
private:
    Handle(V3d_View) myView;
    Handle(AIS_InteractiveContext) myContext;
};

复用 OCCT 7.5+ 的 AIS_ViewController 来处理鼠标交互(节省大量代码)。

6. 主窗口(MainWindow)

  • 左侧 QFormLayout 展示参数控件(QDoubleSpinBox 等);
  • 中间 OcctView
  • 顶部菜单:新建、打开、保存、导出 STEP/glTF/STL、撤销、重做;
  • 状态栏:拾取面信息、几何统计;
  • 参数变化 → FlangeDocument::SetParams → Recompute → AIS Refresh

7. 数据交换菜单

void MainWindow::exportStep() {
    STEPControl_Writer w;
    w.Transfer(myDoc->Result(), STEPControl_AsIs);
    w.Write("flange.step");
}
void MainWindow::exportGltf() {
    BRepMesh_IncrementalMesh(myDoc->Result(), 0.05, false, 0.3, true);
    Handle(TDocStd_Document) tmp;
    XCAFApp_Application::GetApplication()->NewDocument("BinXCAF", tmp);
    auto st = XCAFDoc_DocumentTool::ShapeTool(tmp->Main());
    st->AddShape(myDoc->Result(), false);
    RWGltf_CafWriter w("flange.glb", true);
    TColStd_IndexedDataMapOfStringString info;
    info.Add("Generator", "FlangeApp");
    w.Perform(tmp, info, Message_ProgressRange());
}
void MainWindow::exportStl() {
    BRepMesh_IncrementalMesh(myDoc->Result(), 0.05);
    StlAPI_Writer w; w.ASCIIMode() = false;
    w.Write(myDoc->Result(), "flange.stl");
}

8. 拾取与高亮

ctx->Activate(ais, /*FACE=*/4);
// MouseRelease:
ctx->MoveTo(pos.x(), pos.y(), view, true);
ctx->Select(false);
for (ctx->InitSelected(); ctx->MoreSelected(); ctx->NextSelected()) {
    TopoDS_Shape s = ctx->SelectedShape();
    if (!s.IsNull() && s.ShapeType() == TopAbs_FACE) {
        // 显示属性、面积...
        GProp_GProps g;
        BRepGProp::SurfaceProperties(s, g);
        statusBar()->showMessage(QString("Area=%1").arg(g.Mass()));
    }
}

9. Draw 回归测试

tests/flange.tcl

pload ALL
source flange_lib.tcl
flange f 120 40 20 6 10 80
checkprops f -v 169646
checkprops f -s 38800
checkmaxtol f 1e-3

flange_lib.tcl 通过自定义 plugin 暴露 C++ FlangeBuilder::Build,或直接用 Tcl 拼装算法命令。运行:

DRAWEXE -batch -script flange.tcl

CI 中执行返回码即可作为测试通过/失败标识。

10. 性能与优化

  • 大量螺栓孔时改用一次 BOP,并 SetRunParallel(true)
  • 参数变更但拓扑不变时,只更新 Location
  • AIS 显示模式按用户视图分级:远视 wireframe,近视 shaded;
  • 启用 BRepMesh 缓存:参数变化才 BRepTools::Clean

11. 部署

  • Linux:CMake install;用 cqtdeployer 或自制脚本拷贝 OCCT、Qt、resources/
  • Windows:使用 windeployqt + 手动拷贝 TK*.dll
  • macOS:macdeployqt + install_name_tool
  • Docker:基于 ubuntu:24.04 构建,开放 X 转发或 noVNC 访问;
  • Web:可把核心算法编译为 WASM,前端用 Three.js 显示导出的 glTF。

12. 扩展方向

  • 增加更多零件库(轴承、齿轮、螺栓),每个零件单独的 IBuilder
  • TFunction 把零件和装配的依赖关系自动求解;
  • 与 ERP/PLM 集成:把 OCAF XCAF 中的元数据映射到企业系统;
  • 加入仿真前处理:基于 OCCT 网格生成 SMESH 或 CalculiX 输入;
  • 用 LLM 自动从自然语言生成 FlangeParams,再构造模型;
  • 支持插件机制:用 Plugin_Map 加载第三方零件库。

13. 学习反馈循环

完成本案例后,建议按以下循环深耕:

  1. 在仓库 tests/ 中找到与你算法相关的 grid,跑一遍熟悉行为;
  2. dox/user_guides/ 中找到对应章节,对照源码复习;
  3. 在 OCCT bugtracker / 论坛搜索类似问题;
  4. 写自己的 Draw 命令,做参数扫描;
  5. 阅读关键算法源码(BOPAlgo_PaveFillerChFi3d_BuilderBRepFill_Pipe);
  6. 把改进/修复贡献回 OCCT。

14. 结语

OCCT 是开源 CAD 内核中最完整的一个:从最底层的数学到最上层的应用框架,几乎所有 CAD 软件需要的能力它都有。本教程带你走过 18 章,从全景到底层,从底层回到综合实战。真正掌握 OCCT 需要在每个章节涉及的源码、文档、测试中反复实践——而它给你的回报是:你具备了构建任何 CAD 应用所需的几何与拓扑基础设施。

愿你在 OCCT 的世界里既能解决工程问题,也能享受几何之美。