1. 建模一个瓶子的第一步是建立瓶子底部的轮廓profile。
首先是建立半边轮廓,然后再通过mirror(反射),生成另外一边轮廓,接着就把这两个半边轮廓连接起来形成一个闭环。
在建立半边轮廓时,这个半边轮廓由这几段线段组成:直线段-弧线段-直线段,其中这个弧线段并不能由BRepBuilderAPI_MakeXXX直接生成,package GC提供了一个类GC_MakeArcOfCircle,这个类可以生成圆弧。
设瓶子的宽厚高标识分别为:width,thick, height。
轮廓的尺寸及坐标系见下图:
先定义直线段-弧线段-直线段的点坐标为:
(-width/2, 0, 0), (-width/2, -thick/4, 0)—
(-width/2, -thick/4, 0), (0, -thick/2, 0), (width/2, -thick/4, 0)—
(width/2, -thick/4, 0), (width/2, 0, 0)
直线段可以直接用BRepBuilderAPI_MakeEdge,也可以先使用GC_MakeSegment,返回Handle<Geom_TrimmedCurve>,然后再使用BRepBuilderAPI_MakeEdge,将Handle<Geom_TrimmedCurve>代入,返回TopoDS_Edge。
基本上在所有的建模过程当中,都需要用到TopoDS_Shape继承体系下的类,因为TopoDS_Shape继承体系下的类是描述模型的拓扑信息,这个拓扑信息就相当于模型建模时的骨架,而Geom_Geometry继承体系下的类则是描述模型的几何信息,从结构上来说,几何信息是附属在拓扑信息上的,几何信息是可以更换的。
通过代码:
Handle<Geom_TrimmedCurve> seg1 = GC_MakeSegment(pnt1, pnt2);
Handle<Geom_TrimmedCurve> arcSeg = GC_MakeArcOfCircle(pnt2,pnt3, pnt4);
Handle<Geom_TrimmedCurve> seg2 = GC_MakeSegment(pnt4, pnt5);
创建了这三条线段的几何信息。那么对这些信息的组装则需要添加他们的拓扑信息:
TopoDS_Edge edge1 = BRepBuilderAPI_MakeEdge(seg1);
TopoDS_Edge arcEdge =BRepBuilderAPI_MakeEdge(arcSeg);
TopoDS_Edge edge2 =BRepBuilderAPI_MakeEdge(seg2);
现在需要将这三条拓扑边组成一个环,环有开闭之分,在这里这是一个半边环,所以是开环:
BRepBuilderAPI_MakeWiremkWire(edge1, arcEdge, edge2);
TopoDS_Wire wireHalfProfile =mkWire.Wire();
到现在为止,瓶子底部轮廓的半边已经建好了,这里需要注意的是:在将边组成环的过程中,这些边必须是首位相连的,而且增加边的前后顺序不能打乱!现在就是通过mirror(反射)操作生成轮廓的另一个半边,首先需要设置mirror的参数,镜子放在哪个位置,然后放在什么方向,从上面的示意图可以看出,镜子的位置在原点(0, 0, 0),方向为(1, 0, 0):
gp_Pnt mirrorPnt(0, 0, 0);
gp_Dir mirrorDir(1, 0, 0);
gp_Ax1 mirrorAx(mirrorPnt,mirrorDir);
gp_Trsf mirrorTrsf;
mirrorTrsf.SetMirror(mirrorAx);
在这段代码中,设置原点位置为(0, 0, 0),方向为(1, 0, 0)的gp_Ax1其实有一个很快捷的方式可以用,就是gp::OX():
gp_Ax1 mirrorAx = gp::OX();
在设置好了镜像矩阵后,接下来就运用BRepBuilderAPI_Transform这个类来生成镜像环:
TopoDS_Shape mirrorShape =BRepBuilderAPI_Transform(wireHalfProfile, mirrorTrsf);
TopoDS_ShapewireOtherHalfProfile = TopoDS::Wire(mirrorShape);
两个半边环都生成后,那么现在要做的是将这两个半边环组成一个环:
BRepBuilderAPI_MakeWiremkWire2;
mkWire2.Add(wireHalfProfile);
mkWire2.Add(wireOtherHalfProfile);
TopoDS_Wire profile =mkWire2.Wire();
那么到目前为止瓶子底部轮廓已经生成出来了。
2. 建立瓶身
建立瓶身是采用拉伸的方式,将瓶子的底部等比拉伸一定的高度后形成的体即是瓶身了。
那么首先把瓶子底部轮廓修改成面填充形式,使用BRepBuilderAPI_MakeFace:
TopoDS_FacebottomFace = BRepBuilderAPI_MakeFace(profile);
现在运用OCCT提供的拉伸功能对瓶底面进行拉伸,拉伸出瓶身,拉伸类为BRepPrimAPI_MakePrism,它的拉伸规则如下:
它的拉伸只能沿着一个方向进行等比拉伸。代码如下:
gp_Vec prismVec(0, 0,height);
TopoDS_Shape prismShape= BRepPrimAPI_MakePrism(bottomFace, prismVec);
拉伸出来的瓶身边都是很锋利的,现在需要对边进行圆角化,使用BRepFilletAPI_MakeFillet,需要确定对边进行圆角化时的圆角半径,确定为thick/15。BRepFilletAPI_MakeFillet的使用,先为其传入TopoDS_Shape,然后添加需要进行圆角化的边和半径,最后得到结果。这其中对瓶身的TopoDS_Edge边的遍历可以采用TopExp_Explorer。代码如下:
BRepOffsetAPI_MakeFilletmkFillet(prismShape);
For (TopExp_ExploreredgeExp(prismShape, TopAbs_EDGE); edgeExp.More(); edgeExp.Next())
{
const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current());
mkFillet.Add(thick/15,edge);
}
TopoDS_Shape body = mkFillet.Shape();
#Q: 这里有一个问题,在BRepPrimAPI_MakePrism的拉伸规则中Face是拉伸成Solid的,我将经过拉伸之后的通用结构TopoDS_Shape转换成TopoDS_Solid是可以的,但是在我对其进行圆角之后将其圆角的结果通过结构TopoDS_Shape转换成TopoDS_Solid时会出现异常,这是为什么?
3. 建立瓶颈
瓶颈的一个尺寸是半径为thick/4,高度为height/10,创建瓶颈的一个类是BRepPrimAPI_MakePrism,此类默认创建的瓶颈的位置是在原点创建,我们这个瓶颈的位置应该是在(0, 0, height)。在BRepPrimAPI_MakePrism中要去定圆柱创建的位置需要给它传递一个参数gp_Ax2。
gp_Pnt neckPnt(0, 0,height);
gp_Dir neckDir(0, 0,1);
gp_Ax2 neckAx(neckPnt, neckDir);
double neckRadius = thick/4,neckHeight = height/10;
TopoDS_Shape neck = BRepPrimAPI_MakeCylinder(neckAx,neckRadius, neckHeight);
现在需要把瓶颈和瓶身粘在一起,那么这里就需要用到Boolean Operation,用到其中的交集运算,BRepAlgoAPI_Fuse,代码:
TopoDS_Shape bottle =BRepAlgoAPI_Fuse(body, neck);
在使用BRepAlgoAPI_Fuse进行并集运算时,会去掉body和neck的交集部分。通过渲染线模式可以看到这个结果:
4. 为瓶子建立内胆
现在一个瓶子的雏形已经出来了,现在还需要为这个瓶子建立内胆。同时打开瓶口。打开瓶口则需要找到并去掉瓶子顶部的圆面,打开瓶口并建立内胆这个过程可以使用BRepOffsetAPI_MakeThickSolid来实现,这个类就是为了实现hollowedsolid用的。BRepOffsetAPI_MakeThickSolid需要这些参数:初始化的TopoDS_Shape,需要去掉的面的列表,wall的厚度。那么首先需要找到瓶子顶部的圆面,在这里瓶子顶部的圆面的特征有:这个圆面的位置度在(0, 0, height+neckHeight),并且这个面的性质是一个平面而非曲面。可以看出这两个性质都是属于几何信息,所以这里需要获取TopoDS_Face的几何信息。首先遍历TopoDS_Shape的工具还是使用TopExp_Explorer,初始化使用TopAbs_FACE标识,表示遍历其中的面,然后就是从拓扑信息TopoDS_Face得到几何信息的过程,这个过程需要用到类BRep_Tools,使用BRep_Tool::Surface方法。这个方法返回的是几何结构面的基类Geom_Surface,在我们这个例子中,需要判断这个面是曲面还是平面,平面才是我们需要的面,这个时候需要用到它的基类Standard_Transient的功能:数据类型识别,类型强制转换的功能。代码如下:
TopTools_ListOfShapefacesRemoved;
for (TopExp_Explorer faceExp(bottle, TopAbs_FACE); faceExp.More(); faceExp.Next())
{
TopoDS_Faceface = TopoDS::Face(faceExp.Current());
Handle(Geom_Surface)surface = BRep_Tool::Surface()
if (surface->DynamicType()== STANDARD_TYPE(Geom_Plane))
{
Handle(Geom_Plane)plane = Handle(Geom_Plane)::DownCast(surface);
gp_PntlocPnt = plane->Location();
if(locPnt.Z() > height) facesRemoved.Append(face);
}
}
bottle =BRepOffsetAPI_MakeThickSolid(bottle, facesRemoved, -thick/50, 1.3e-3);
5. 为瓶口建立螺纹
#TODO(continue)
6. 代码:
TopoDS_Shape reCreateBottle(const double width, const double thick, const double height)
{
gp_Pnt pnt1(-width/2, 0, 0),
pnt2(-width/2, -thick/4, 0),
pnt3(0, -thick/2, 0),
pnt4(width/2, -thick/4, 0),
pnt5(width/2, 0, 0);
// create geometry data of segment and arc segment
Handle(Geom_TrimmedCurve) seg1 = GC_MakeSegment(pnt1, pnt2);
Handle(Geom_TrimmedCurve) arcSeg = GC_MakeArcOfCircle(pnt2, pnt3, pnt4);
Handle(Geom_TrimmedCurve) seg2 = GC_MakeSegment(pnt4, pnt5);
// create topological data with above geometry data
TopoDS_Edge edge1 = BRepBuilderAPI_MakeEdge(seg1);
TopoDS_Edge arcEdge = BRepBuilderAPI_MakeEdge(arcSeg);
TopoDS_Edge edge2 = BRepBuilderAPI_MakeEdge(seg2);
// make wire composited by above topological data
TopoDS_Wire wireHalfProfile = BRepBuilderAPI_MakeWire(edge1, arcEdge, edge2);
// make mirror wire
gp_Ax1 mirrorAx = gp::OX();
gp_Trsf mirrorTrsf;
mirrorTrsf.SetMirror(mirrorAx);
TopoDS_Shape mirrorShape = BRepBuilderAPI_Transform(wireHalfProfile, mirrorTrsf);
TopoDS_Wire wireOtherHalfProfile = TopoDS::Wire(mirrorShape);
// composite two half wire to bottle bottom profile
BRepBuilderAPI_MakeWire mkWire;
mkWire.Add(wireHalfProfile);
mkWire.Add(wireOtherHalfProfile);
TopoDS_Wire wireProfile = mkWire.Wire();
// create bottle body
TopoDS_Face bottomFace = BRepBuilderAPI_MakeFace(wireProfile);
gp_Vec prismVec(0, 0, height);
TopoDS_Shape prismShape = BRepPrimAPI_MakePrism(bottomFace, prismVec);
//----fillet to round edge
BRepFilletAPI_MakeFillet mkFillet(prismShape);
for (TopExp_Explorer edgeExp(prismShape, TopAbs_EDGE); edgeExp.More(); edgeExp.Next())
{
const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current());
mkFillet.Add(thick/15, edge);;
}
TopoDS_Shape body = mkFillet.Shape();
// create neck
gp_Pnt neckPnt(0, 0, height);
gp_Dir neckDir(0, 0, 1);
gp_Ax2 neckAx(neckPnt, neckDir);
double neckRadius = thick/4, neckHeight = height/10;
TopoDS_Shape neck = BRepPrimAPI_MakeCylinder(neckAx, neckRadius, neckHeight);
// apply boolean operation fuse, union body and neck
TopoDS_Shape bottle = BRepAlgoAPI_Fuse(body, neck);
// make thick solid
TopTools_ListOfShape facesRemoved;
for (TopExp_Explorer faceExp(bottle, 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))
{
Handle(Geom_Plane) plane = Handle(Geom_Plane)::DownCast(surface);
gp_Pnt locPnt = plane->Location();
if (locPnt.Z() > height)
facesRemoved.Append(face);
}
}
bottle = BRepOffsetAPI_MakeThickSolid(bottle, facesRemoved, -thick/50, 1.e-3);
return bottle;
}