换句话说就是如何使用手动调整的范围框来定义视图剖视框,即模型是如何在三维视图中被剪切的。
问题
下图是一个模型的三维视图,其中有一个虚线表示的范围框。三维视图的 SectionBox 属性被选中,所以视图的剖视框(实线表示)也显示出来了。虚线范围框和实线剖视框都被选中。
Jeremy
你需要实现如下的操作步骤:
1. 从范围框获取所需几何数据
范围框没有提供直接的 Location 属性,所以只能从它的几何定义中计算得到。通过 RevitLookup 我们可以发现,范围框包含12条线段(即范围框的12条边)。所以你需要通过这12条线段来范围框的计算尺寸和方向,进而计算视图剖视框。
2. 创建需要的剖视框对象(转换、最大坐标值、最小坐标值……)
参见 create a section view parallel to a wall3. 将剖视框对象设置到视图的 SectionBox 属性
view.SectionBox = newSectionBox
确认坐标系为右手螺旋方向
当且仅当坐标系确定的平行六面体的有符号体积为正值时,该坐标系为右手螺旋方向。有符号体积的计算公式为:前两个坐标轴向量的叉积与第三个坐标轴的点积。
/// <summary> /// 由向量 a,b,c 围成的平行六面体的有符号体积。德语称之为 Spatprodukt。 /// </summary> static double SignedParallelipedVolume( XYZ a, XYZ b, XYZ c ) { return a.CrossProduct( b ).DotProduct( c ); } /// <summary> /// 如果三个向量 a,b,c 组成右手螺旋方向的坐标系,则返回 true。 /// 即由这三个向量围成的平行六面体的有符号体积为正值。 /// </summary> bool IsRightHanded( XYZ a, XYZ b, XYZ c ) { return 0 < SignedParallelipedVolume( a, b, c ); }
获取范围框的 Bounding Box
以上准备工作就绪之后,是可以使用 GetScopeBoxBoundingBox() 方法获取 Bounding Box 了。
BoundingBoxXYZ GetScopeBoxBoundingBox( Element scopeBox ) { Document doc = scopeBox.Document; Application app = doc.Application; Options opt = app.Create.NewGeometryOptions(); GeometryElement geo = scopeBox.get_Geometry( opt ); int n = geo.Count<GeometryObject>(); if( 12 != n ) { throw new ArgumentException( "Expected exactly 12 lines in scope box geometry" ); } XYZ origin = null; XYZ vx = null; XYZ vy = null; XYZ vz = null; // 从平行六面体的12条边中获取X/Y/Z轴 foreach( GeometryObject obj in geo ) { Debug.Assert( obj is Line, "expected only lines in scope box geometry" ); Line line = obj as Line; XYZ p = line.get_EndPoint( 0 ); XYZ q = line.get_EndPoint( 1 ); XYZ v = q - p; if( null == origin ) { origin = p; vx = v; } else if( p.IsAlmostEqualTo( origin ) || q.IsAlmostEqualTo( origin ) ) { if( q.IsAlmostEqualTo( origin ) ) { v = v.Negate(); } if( null == vy ) { Debug.Assert( IsPerpendicular( vx, v ), "expected orthogonal lines in scope box geometry" ); vy = v; } else { Debug.Assert( null == vz, "expected exactly three orthogonal lines to originate in one point" ); Debug.Assert( IsPerpendicular( vx, v ), "expected orthogonal lines in scope box geometry" ); Debug.Assert( IsPerpendicular( vy, v ), "expected orthogonal lines in scope box geometry" ); vz = v; if( !( IsRightHanded( vx, vy, vz ) ) ) { XYZ tmp = vz; vz = vy; vy = tmp; } break; } } } // 创建转换(Transform) Transform t = Transform.Identity; t.Origin = origin; t.BasisX = vx.Normalize(); t.BasisY = vy.Normalize(); t.BasisZ = vz.Normalize(); Debug.Assert( t.IsConformal, "expected resulting transform to be conformal" ); // 创建 Bounding Box BoundingBoxXYZ bb = new BoundingBoxXYZ(); bb.Transform = t; bb.Min = XYZ.Zero; bb.Max = vx + vy + vz; return bb; }
根据范围框计算合适的视图剖视框
现在我需要确认Z轴确实是垂直向上的。在考虑视图方向的前提下,使用最靠近观察者的范围框边界作为剖视框的Z轴。
使用视图方向和范围框 Bounding Box 的最大尺寸来共同确定视点。我们将身处视点来观测范围框。我将会遍历两次范围框的边界集合。在第一次遍历中,我确定原点和Z轴。在第二次遍历中,我确定Y轴和Z轴。
BoundingBoxXYZ GetSectionBoundingBoxFromScopeBox( Element scopeBox, XYZ viewdirTowardViewer ) { Document doc = scopeBox.Document; Application app = doc.Application; // 从观察者的角度在范围框的外部找到一个可能的视点 BoundingBoxXYZ bb = scopeBox.get_BoundingBox( null ); XYZ v = bb.Max - bb.Min; double size = v.GetLength(); XYZ viewPoint = bb.Min + 10 * size * viewdirTowardViewer; // 获取范围框几何数据(即它的12条边界) Options opt = app.Create.NewGeometryOptions(); GeometryElement geo = scopeBox.get_Geometry( opt ); int n = geo.Count<GeometryObject>(); if( 12 != n ) { throw new ArgumentException( "Expected exactly 12 lines in scope box geometry" ); } // 将最接近观察者的那条边界的底部端点作为原点,从原点出发垂直向上的向量作为Z轴。 // 如果和观察者距离最近的边界多于一条,则选择最左边的那条(假设给定的视图方向中Z轴是垂直向上的) double dist = double.MaxValue; XYZ origin = null; XYZ vx = null; XYZ vy = null; XYZ vz = null; XYZ p, q; foreach( GeometryObject obj in geo ) { Debug.Assert( obj is Line, "expected only lines in scope box geometry" ); Line line = obj as Line; p = line.get_EndPoint( 0 ); q = line.get_EndPoint( 1 ); v = q - p; if( IsVertical( v ) ) { if( q.Z < p.Z ) { p = q; v = v.Negate(); } if( p.DistanceTo( viewPoint ) < dist ) { origin = p; dist = origin.DistanceTo( viewPoint ); vz = v; } } } // 找到另外两条以原点为端点的边界作为X轴和Y轴,并确认X/Y/Z组成符合右手螺旋方向的坐标系 foreach( GeometryObject obj in geo ) { Line line = obj as Line; p = line.get_EndPoint( 0 ); q = line.get_EndPoint( 1 ); v = q - p; if( IsVertical( v ) ) // 已经在上面的遍历中处理过了 { continue; } if( p.IsAlmostEqualTo( origin ) || q.IsAlmostEqualTo( origin ) ) { if( q.IsAlmostEqualTo( origin ) ) { v = v.Negate(); } if( null == vx ) { Debug.Assert( IsPerpendicular( vz, v ), "expected orthogonal lines in scope box geometry" ); vx = v; } else { Debug.Assert( null == vy, "expected exactly three orthogonal lines to originate in one point" ); Debug.Assert( IsPerpendicular( vz, v ), "expected orthogonal lines in scope box geometry" ); Debug.Assert( IsPerpendicular( vx, v ), "expected orthogonal lines in scope box geometry" ); vy = v; if( !( IsRightHanded( vx, vy, vz ) ) ) { XYZ tmp = vx; vx = vy; vy = tmp; } break; } } } // 创建转换(Transform) Transform t = Transform.Identity; t.Origin = origin; t.BasisX = vx.Normalize(); t.BasisY = vy.Normalize(); t.BasisZ = vz.Normalize(); Debug.Assert( t.IsConformal, "expected resulting transform to be conformal" ); // 创建 Bounding Box bb = new BoundingBoxXYZ(); bb.Transform = t; bb.Min = XYZ.Zero; bb.Max = vx + vy + vz; return bb; }
集成测试
创建一个外部命令,在当前的三维视图中首先找到第一个范围框元素,然后执行如下操作:
1. 访问当前视图并确认是否为三维视图;UIApplication uiapp = commandData.Application; UIDocument uidoc = uiapp.ActiveUIDocument; Application app = uiapp.Application; Document doc = uidoc.Document; View3D view = doc.ActiveView as View3D; if( null == view ) { message = "Please run this command in a 3D view."; return Result.Failed; } Element scopeBox = new FilteredElementCollector( doc, view.Id ) .OfCategory( BuiltInCategory.OST_VolumeOfInterest ) .WhereElementIsNotElementType() .FirstElement(); BoundingBoxXYZ viewSectionBox = GetSectionBoundingBoxFromScopeBox( scopeBox, view.ViewDirection ); using( Transaction tx = new Transaction( doc ) ) { tx.Start( "Move And Resize Section Box" ); view.SectionBox = viewSectionBox; tx.Commit(); } return Result.Succeeded;