用Open CASCADE从零到一:手把手教你用C++代码‘捏’一个3D瓶子模型

发布时间:2026/6/2 23:09:48

用Open CASCADE从零到一:手把手教你用C++代码‘捏’一个3D瓶子模型 用Open CASCADE从零到一手把手教你用C代码‘捏’一个3D瓶子模型在数字创作的世界里3D建模往往让人联想到复杂的专业软件界面和繁琐的鼠标操作。但今天我们将打破这种刻板印象用程序员最熟悉的武器——代码来创造一个有形的3D物体。这就像用数字黏土雕塑作品只不过我们的工具是C和Open CASCADEOCCT这个强大的几何内核。Open CASCADE是一个开源的C库专为CAD/CAM/CAE应用程序开发而设计。与常见的图形API如OpenGL不同OCCT提供了更高层次的几何抽象让我们能够像真正的工程师一样思考形状的构造。想象一下你不再是被动地使用建模工具而是可以直接用数学语言描述物体的几何本质——这就是编程建模的魅力所在。1. 环境准备与基础概念在开始捏我们的数字瓶子之前需要确保开发环境准备就绪。推荐使用支持C17的编译器如GCC 9或MSVC 2019并通过CMake管理项目。Open CASCADE可以通过官网下载预编译库或从源码构建。# 示例CMake配置片段 find_package(OpenCASCADE REQUIRED) add_executable(BottleModeler main.cpp) target_link_libraries(BottleModeler OpenCASCADE::TKernel OpenCASCADE::TKG3d)OCCT的核心概念可以分为几何(Geometric)和拓扑(Topologic)两个层面几何元素处理数学描述gp_Pnt表示3D空间中的点gp_Vec表示向量Geom_Curve及其派生类描述曲线几何拓扑结构定义形状关系TopoDS_Vertex对应几何点的零维形状TopoDS_Edge基于曲线的一维形状TopoDS_Wire由边连接成的环TopoDS_Face由线包围的曲面部分理解这些基础概念后我们可以开始构建瓶子的轮廓。就像雕塑家先勾勒大致形状一样我们将从二维轮廓开始然后通过拉伸等操作赋予其体积。2. 构建瓶子基础轮廓瓶子的轮廓设计从定义关键点开始。假设我们要创建一个高70mm、宽50mm、厚度30mm的瓶子首先在XOY平面定义五个特征点Standard_Real myWidth 50.0; Standard_Real myHeight 70.0; Standard_Real myThickness 30.0; gp_Pnt aPnt1(-myWidth/2, 0, 0); // 左端点 gp_Pnt aPnt2(-myWidth/2, -myThickness/4, 0); gp_Pnt aPnt3(0, -myThickness/2, 0); // 底部中心点 gp_Pnt aPnt4(myWidth/2, -myThickness/4, 0); gp_Pnt aPnt5(myWidth/2, 0, 0); // 右端点将这些点连接起来形成轮廓曲线。左侧使用线段底部使用圆弧右侧同样使用线段// 创建底部圆弧 Handle(Geom_TrimmedCurve) anArc GC_MakeArcOfCircle(aPnt2, aPnt3, aPnt4); // 创建左右线段 Handle(Geom_TrimmedCurve) aSegment1 GC_MakeSegment(aPnt1, aPnt2); Handle(Geom_TrimmedCurve) aSegment2 GC_MakeSegment(aPnt4, aPnt5);将这些几何曲线转换为拓扑边然后组合成闭合环TopoDS_Edge edge1 BRepBuilderAPI_MakeEdge(aSegment1); TopoDS_Edge edge2 BRepBuilderAPI_MakeEdge(anArc); TopoDS_Edge edge3 BRepBuilderAPI_MakeEdge(aSegment2); TopoDS_Wire halfWire BRepBuilderAPI_MakeWire(edge1, edge2, edge3);为了得到完整的对称轮廓我们通过镜像操作复制另一半gp_Ax1 mirrorAxis gp::OX(); // 以X轴为镜像轴 gp_Trsf mirrorTransform; mirrorTransform.SetMirror(mirrorAxis); BRepBuilderAPI_Transform mirrorMaker(halfWire, mirrorTransform); TopoDS_Wire fullWire BRepBuilderAPI_MakeWire( halfWire, TopoDS::Wire(mirrorMaker.Shape()) );现在我们已经有了一个完整的瓶子底部轮廓线。这个阶段就像完成了瓶子的二维设计图纸接下来要将其转化为三维实体。3. 从2D到3D构建瓶体将二维轮廓转化为三维实体最直接的方法是拉伸Prism。首先需要将轮廓线转换为面TopoDS_Face profileFace BRepBuilderAPI_MakeFace(fullWire);然后沿Z轴方向拉伸这个面形成实体瓶身gp_Vec extrusionVec(0, 0, myHeight); TopoDS_Shape bottleBody BRepPrimAPI_MakePrism(profileFace, extrusionVec);此时的瓶子边缘还很锐利我们需要添加圆角使其更真实。OCCT提供了方便的圆角工具BRepFilletAPI_MakeFillet filletMaker(bottleBody); // 遍历所有边并添加圆角 TopExp_Explorer edgeExplorer(bottleBody, TopAbs_EDGE); while(edgeExplorer.More()) { filletMaker.Add(myThickness/12, TopoDS::Edge(edgeExplorer.Current())); edgeExplorer.Next(); } bottleBody filletMaker.Shape();接下来添加瓶颈部分。瓶颈是一个圆柱体位于瓶体顶部中心gp_Pnt neckLocation(0, 0, myHeight); gp_Ax2 neckAxis(neckLocation, gp::DZ()); Standard_Real neckRadius myThickness/4; Standard_Real neckHeight myHeight/10; TopoDS_Shape neck BRepPrimAPI_MakeCylinder(neckAxis, neckRadius, neckHeight); // 将瓶颈与瓶体融合 bottleBody BRepAlgoAPI_Fuse(bottleBody, neck);真正的瓶子需要能够盛装液体因此我们需要将实体挖空。OCCT提供了创建空心实体的工具// 寻找要移除的顶面 TopoDS_Face faceToRemove; Standard_Real maxZ -1; for(TopExp_Explorer faceExp(bottleBody, TopAbs_FACE); faceExp.More(); faceExp.Next()) { TopoDS_Face face TopoDS::Face(faceExp.Current()); Handle(Geom_Surface) surface BRep_Tool::Surface(face); if(surface-DynamicType() STANDARD_TYPE(Geom_Plane)) { gp_Pnt loc Handle(Geom_Plane)::DownCast(surface)-Location(); if(loc.Z() maxZ) { maxZ loc.Z(); faceToRemove face; } } } // 创建空心实体 TopTools_ListOfShape facesToRemove; facesToRemove.Append(faceToRemove); BRepOffsetAPI_MakeThickSolid hollowMaker; hollowMaker.MakeThickSolidByJoin(bottleBody, facesToRemove, -myThickness/50, 1e-3); bottleBody hollowMaker.Shape();至此我们已经完成了瓶子的主体结构。接下来将为瓶颈添加螺纹细节这是最有趣也最具挑战性的部分。4. 创建瓶口螺纹螺纹的创建需要一些技巧因为它是缠绕在圆柱面上的螺旋形状。我们将使用两个不同半径的圆柱面作为螺纹的基础Handle(Geom_CylindricalSurface) cyl1 new Geom_CylindricalSurface(neckAxis, neckRadius*0.99); Handle(Geom_CylindricalSurface) cyl2 new Geom_CylindricalSurface(neckAxis, neckRadius*1.05);在参数空间定义螺纹的二维轮廓。这里使用椭圆弧作为螺纹截面gp_Pnt2d center(2*M_PI, neckHeight/2); gp_Dir2d xDir(2*M_PI, neckHeight/4); gp_Ax2d profileAxes(center, xDir); Handle(Geom2d_Ellipse) ellipse1 new Geom2d_Ellipse(profileAxes, 2*M_PI, neckHeight/10); Handle(Geom2d_Ellipse) ellipse2 new Geom2d_Ellipse(profileAxes, 2*M_PI, neckHeight/40); // 修剪椭圆为半圆 Handle(Geom2d_TrimmedCurve) arc1 new Geom2d_TrimmedCurve(ellipse1, 0, M_PI); Handle(Geom2d_TrimmedCurve) arc2 new Geom2d_TrimmedCurve(ellipse2, 0, M_PI);将这些2D曲线投影到3D圆柱面上创建边TopoDS_Edge edgeOnCyl1 BRepBuilderAPI_MakeEdge(arc1, cyl1); TopoDS_Edge edgeOnCyl2 BRepBuilderAPI_MakeEdge(arc2, cyl2); // 创建连接线段 gp_Pnt2d p1 ellipse1-Value(0); gp_Pnt2d p2 ellipse1-Value(M_PI); Handle(Geom2d_TrimmedCurve) segment GCE2d_MakeSegment(p1, p2); TopoDS_Edge segEdge1 BRepBuilderAPI_MakeEdge(segment, cyl1); TopoDS_Edge segEdge2 BRepBuilderAPI_MakeEdge(segment, cyl2);将这些边组合成线然后通过放样操作创建实体螺纹TopoDS_Wire wire1 BRepBuilderAPI_MakeWire(edgeOnCyl1, segEdge1); TopoDS_Wire wire2 BRepBuilderAPI_MakeWire(edgeOnCyl2, segEdge2); // 构建3D曲 BRepLib::BuildCurves3d(wire1); BRepLib::BuildCurves3d(wire2); // 通过放样创建螺纹实体 BRepOffsetAPI_ThruSections loftMaker(Standard_True); loftMaker.AddWire(wire1); loftMaker.AddWire(wire2); loftMaker.CheckCompatibility(Standard_False); TopoDS_Shape threading loftMaker.Shape();最后将瓶体和螺纹组合成一个完整的模型TopoDS_Compound result; BRep_Builder builder; builder.MakeCompound(result); builder.Add(result, bottleBody); builder.Add(result, threading);这个完整的建模过程展示了如何用代码一步步构建复杂的3D形状。与传统的GUI建模相比编程建模提供了无与伦比的精确性和可重复性。你可以轻松调整参数如瓶子的高度、宽度或螺纹的细节而无需手动重新建模。

相关新闻