天天看点

【第二回】使用OCCT实现对一个瓶子建模的总结

资料:Open CASCADE Technology : Turorial

1.      建模一个瓶子的第一步是建立瓶子底部的轮廓profile。

首先是建立半边轮廓,然后再通过mirror(反射),生成另外一边轮廓,接着就把这两个半边轮廓连接起来形成一个闭环。

在建立半边轮廓时,这个半边轮廓由这几段线段组成:直线段-弧线段-直线段,其中这个弧线段并不能由BRepBuilderAPI_MakeXXX直接生成,package GC提供了一个类GC_MakeArcOfCircle,这个类可以生成圆弧。

设瓶子的宽厚高标识分别为:width,thick, height。

轮廓的尺寸及坐标系见下图:

【第二回】使用OCCT实现对一个瓶子建模的总结

先定义直线段-弧线段-直线段的点坐标为:

(-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,它的拉伸规则如下:

【第二回】使用OCCT实现对一个瓶子建模的总结

它的拉伸只能沿着一个方向进行等比拉伸。代码如下:

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的交集部分。通过渲染线模式可以看到这个结果:

【第二回】使用OCCT实现对一个瓶子建模的总结

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;
}