一、前言
上一篇写了Solid的创建、展示、变换、布尔操作,这一篇写另一种Solid的创建方法。
需要再次强调的是,BrepBuilder不适合用来创建Solid。
二、边界表示
【Wiki】:边界表示(Boundary representation,简称B-Rep),是一种通过定义其体积限制来表示3D形状的方法。实体表示为连接的曲面元素的集合,这些曲面元素定义内部点和外部点之间的边界。
还有个叫 CSG(Constructive Solid Geometry)的,和一些其它表示方法。我不知道Revit用的是哪种,当然了,API调用仔调API就行了,也只能按开放的接口用。如果不爽调用这破API,可以去看“Open Cascade”,整会了就可以自个儿玩了。
Brep,简单来讲,就是定义每个面,定义面上的范围(边),再把它们严丝合缝的拼起来。
“Geometric Modeling”里面介绍了一些几何建模的方法,了解一下就行了。
三、BRepBuilder
3.1. 使用流程
以创建一个正方体为例。
Ⅰ 准备阶段:
- 创建
BRepBuilder
实例 - 创建
Surface
,即前后左右上下6个平面。 - 创建
Curve
,即正方体12条直线边。
Ⅱ 组装填充阶段:
AddFace
AddEdge
AddLoop
AddCoEdge
FinishLoop
,FinishFace
Finish
3.2. 样例代码解释
正方体生成的代码和其它例子,可以查看SDK目录下的“SDKSample/BRepBuilderExample/CreateCube.cs”,或者查看GitHub。
直接看代码可能有点迷糊,这里对组装填充的步骤进行解释。
请结合图来看。
// 摘自 RevitSdkSamples
private BRepBuilder CreateCubeImpl()
{
// create a BRepBuilder; add faces to build a cube
// BRepType.Solid,表示面都需要朝外;朝内就是Void
BRepBuilder brepBuilder = new BRepBuilder(BRepType.Solid);
// a cube 100x100x100, from (0,0,0) to (100, 100, 100)
// 1. Planes.
// 6个面,面的朝向如图
Plane bottom = Plane.CreateByOriginAndBasis(new XYZ(50, 50, 0), new XYZ(1, 0, 0), new XYZ(0, 1, 0)); // bottom. XY plane, Z = 0, normal pointing inside the cube.
Plane top = Plane.CreateByOriginAndBasis(new XYZ(50, 50, 100), new XYZ(1, 0, 0), new XYZ(0, 1, 0)); // top. XY plane, Z = 100, normal pointing outside the cube.
Plane front = Plane.CreateByOriginAndBasis(new XYZ(100, 50, 50), new XYZ(0, 0, 1), new XYZ(0, 1, 0)); // front side. ZY plane, X = 0, normal pointing inside the cube.
Plane back = Plane.CreateByOriginAndBasis(new XYZ(0, 50, 50), new XYZ(0, 0, 1), new XYZ(0, 1, 0)); // back side. ZY plane, X = 0, normal pointing outside the cube.
Plane left = Plane.CreateByOriginAndBasis(new XYZ(50, 0, 50), new XYZ(0, 0, 1), new XYZ(1, 0, 0)); // left side. ZX plane, Y = 0, normal pointing inside the cube
Plane right = Plane.CreateByOriginAndBasis(new XYZ(50, 100, 50), new XYZ(0, 0, 1), new XYZ(1, 0, 0)); // right side. ZX plane, Y = 100, normal pointing outside the cube
// 2. Faces.
// 填充Face,注意参数True/False,表示面是否需要翻转
// 面需要朝外,看图,bottom/front/left 都需要翻转面朝向
BRepBuilderGeometryId faceId_Bottom = brepBuilder.AddFace(BRepBuilderSurfaceGeometry.Create(bottom, null), true);
BRepBuilderGeometryId faceId_Top = brepBuilder.AddFace(BRepBuilderSurfaceGeometry.Create(top, null), false);
BRepBuilderGeometryId faceId_Front = brepBuilder.AddFace(BRepBuilderSurfaceGeometry.Create(front, null), true);
BRepBuilderGeometryId faceId_Back = brepBuilder.AddFace(BRepBuilderSurfaceGeometry.Create(back, null), false);
BRepBuilderGeometryId faceId_Left = brepBuilder.AddFace(BRepBuilderSurfaceGeometry.Create(left, null), true);
BRepBuilderGeometryId faceId_Right = brepBuilder.AddFace(BRepBuilderSurfaceGeometry.Create(right, null), false);
// 3. Edges.
// 3.a (define edge geometry)
// 定义12条边,边的方向如图
// walk around bottom face
BRepBuilderEdgeGeometry edgeBottomFront = BRepBuilderEdgeGeometry.Create(new XYZ(100, 0, 0), new XYZ(100, 100, 0));
BRepBuilderEdgeGeometry edgeBottomRight = BRepBuilderEdgeGeometry.Create(new XYZ(100, 100, 0), new XYZ(0, 100, 0));
BRepBuilderEdgeGeometry edgeBottomBack = BRepBuilderEdgeGeometry.Create(new XYZ(0, 100, 0), new XYZ(0, 0, 0));
BRepBuilderEdgeGeometry edgeBottomLeft = BRepBuilderEdgeGeometry.Create(new XYZ(0, 0, 0), new XYZ(100, 0, 0));
// now walk around top face
BRepBuilderEdgeGeometry edgeTopFront = BRepBuilderEdgeGeometry.Create(new XYZ(100, 0, 100), new XYZ(100, 100, 100));
BRepBuilderEdgeGeometry edgeTopRight = BRepBuilderEdgeGeometry.Create(new XYZ(100, 100, 100), new XYZ(0, 100, 100));
BRepBuilderEdgeGeometry edgeTopBack = BRepBuilderEdgeGeometry.Create(new XYZ(0, 100, 100), new XYZ(0, 0, 100));
BRepBuilderEdgeGeometry edgeTopLeft = BRepBuilderEdgeGeometry.Create(new XYZ(0, 0, 100), new XYZ(100, 0, 100));
// sides
BRepBuilderEdgeGeometry edgeFrontRight = BRepBuilderEdgeGeometry.Create(new XYZ(100, 100, 0), new XYZ(100, 100, 100));
BRepBuilderEdgeGeometry edgeRightBack = BRepBuilderEdgeGeometry.Create(new XYZ(0, 100, 0), new XYZ(0, 100, 100));
BRepBuilderEdgeGeometry edgeBackLeft = BRepBuilderEdgeGeometry.Create(new XYZ(0, 0, 0), new XYZ(0, 0, 100));
BRepBuilderEdgeGeometry edgeLeftFront = BRepBuilderEdgeGeometry.Create(new XYZ(100, 0, 0), new XYZ(100, 0, 100));
// 3.b (define the edges themselves)
// 填充边
BRepBuilderGeometryId edgeId_BottomFront = brepBuilder.AddEdge(edgeBottomFront);
BRepBuilderGeometryId edgeId_BottomRight = brepBuilder.AddEdge(edgeBottomRight);
BRepBuilderGeometryId edgeId_BottomBack = brepBuilder.AddEdge(edgeBottomBack);
BRepBuilderGeometryId edgeId_BottomLeft = brepBuilder.AddEdge(edgeBottomLeft);
BRepBuilderGeometryId edgeId_TopFront = brepBuilder.AddEdge(edgeTopFront);
BRepBuilderGeometryId edgeId_TopRight = brepBuilder.AddEdge(edgeTopRight);
BRepBuilderGeometryId edgeId_TopBack = brepBuilder.AddEdge(edgeTopBack);
BRepBuilderGeometryId edgeId_TopLeft = brepBuilder.AddEdge(edgeTopLeft);
BRepBuilderGeometryId edgeId_FrontRight = brepBuilder.AddEdge(edgeFrontRight);
BRepBuilderGeometryId edgeId_RightBack = brepBuilder.AddEdge(edgeRightBack);
BRepBuilderGeometryId edgeId_BackLeft = brepBuilder.AddEdge(edgeBackLeft);
BRepBuilderGeometryId edgeId_LeftFront = brepBuilder.AddEdge(edgeLeftFront);
// 4. Loops.
// 填充轮廓
BRepBuilderGeometryId loopId_Bottom = brepBuilder.AddLoop(faceId_Bottom);
BRepBuilderGeometryId loopId_Top = brepBuilder.AddLoop(faceId_Top);
BRepBuilderGeometryId loopId_Front = brepBuilder.AddLoop(faceId_Front);
BRepBuilderGeometryId loopId_Back = brepBuilder.AddLoop(faceId_Back);
BRepBuilderGeometryId loopId_Right = brepBuilder.AddLoop(faceId_Right);
BRepBuilderGeometryId loopId_Left = brepBuilder.AddLoop(faceId_Left);
// 5. Co-edges.
// 定义轮廓,即 向loop中填充边,
// ① 边需要首尾相连;② 边按右手螺旋定则,方向需要和面一致(即朝外)
// Bottom face. All edges reversed
brepBuilder.AddCoEdge(loopId_Bottom, edgeId_BottomFront, true); // other direction in front loop
brepBuilder.AddCoEdge(loopId_Bottom, edgeId_BottomLeft, true); // other direction in left loop
brepBuilder.AddCoEdge(loopId_Bottom, edgeId_BottomBack, true); // other direction in back loop
brepBuilder.AddCoEdge(loopId_Bottom, edgeId_BottomRight, true); // other direction in right loop
brepBuilder.FinishLoop(loopId_Bottom); // 完成底面轮廓的定义
brepBuilder.FinishFace(faceId_Bottom); // 完成底面的定义
// Top face. All edges NOT reversed.
brepBuilder.AddCoEdge(loopId_Top, edgeId_TopFront, false); // other direction in front loop.
brepBuilder.AddCoEdge(loopId_Top, edgeId_TopRight, false); // other direction in right loop
brepBuilder.AddCoEdge(loopId_Top, edgeId_TopBack, false); // other direction in back loop
brepBuilder.AddCoEdge(loopId_Top, edgeId_TopLeft, false); // other direction in left loop
brepBuilder.FinishLoop(loopId_Top);
brepBuilder.FinishFace(faceId_Top);
// Front face.
// 略,详情看源码
// Back face
// Right face
// Left face
brepBuilder.Finish(); // 完成构建
return brepBuilder;
}
// 获取构件结果
Solid mySolid = CreateCubeImpl().GetResult();
3.3. API实用性分析
第一点就是使用繁杂,容易出错。我们看到了,即使是生成一个最简单的正方体,都要大费周章。
而若是使用上一篇写的“GeometryCreationUtilities”呢?
public Solid CreateBox()
{
XYZ point1 = XYZ.Zero;
XYZ point2 = new XYZ(100, 0, 0);
XYZ point3 = new XYZ(100, 100, 0);
XYZ point4 = new XYZ(0, 100, 0);
CurveLoop curveLoop = new CurveLoop();
curveLoop.Append(Line.CreateBound(point1, point2));
curveLoop.Append(Line.CreateBound(point2, point3));
curveLoop.Append(Line.CreateBound(point3, point4));
curveLoop.Append(Line.CreateBound(point4, point1));
Solid solid = GeometryCreationUtilities.CreateExtrusionGeometry(new List<CurveLoop>() { curveLoop }, XYZ.BasisZ, 100);
return solid;
}
第二点就是使用难度高。正方体是简单,但若复杂一点呢?曲面、锥面、直纹面、赫尔米特曲面的组合呢,这些面都有现成的API,但问题在于“Edge”,如何去计算多个曲面的交界边是BrepBuilder使用难度高的主要原因。
我这小卡拉米,是算不出曲面与曲面的交线的,就算是最简单的一个平面斜切圆柱,都得写写画画好一会儿。
虽然 Revit Face
类提供了计算面交线的方法,但是Surface
可没有。Face没有构造方法,只能从Solid上获取。而要使用Brep生成Solid,就需要知道面的交线。这样就死循环了,所以还是得算,这也就是使用难度高。
当然了,该方法有时还是挺好用的,比如说,对一个现有的曲面进行拉伸。
四、总结
这一篇简单写了BrepBilder
的使用流程,结合示例进行了解释。