原文链接: Set View Section Box to Match Scope Box

今天我们讨论一个我很感兴趣的问题:
1. 如何精确地获取空间范围框(Scope Box)的几何位置、尺寸和方向?
2. 如何精确地设置三维视图剖视框(Section Box)的几何位置、尺寸和方向?

换句话说就是如何使用手动调整的范围框来定义视图剖视框,即模型是如何在三维视图中被剪切的。

实际上我已经在博文 create a section view parallel to a wall (译者注:我的翻译版本在这里 创建与墙体平行的剖视图) 中说明了如何设置一个视图剖视框。关键在于正确地设置视图的 SectionBox 属性。该属性是一个 BoundingBoxXYZ 类型的值,即一个转换(Transform)加上最大坐标值和最小坐标值。该属性描述了范围框的位置、方向和尺寸。在那篇博文里,这个属性值被传入 ViewSection.CreateSection() 方法用于创建剖视图。

如果需要修改(而不是创建)一个存在的剖视图,我们只要将更新后的 BoundingBoxXYZ 赋予该剖视图的 SectionBox 属性即可。

问题

下图是一个模型的三维视图,其中有一个虚线表示的范围框。三维视图的 SectionBox 属性被选中,所以视图的剖视框(实线表示)也显示出来了。虚线范围框和实线剖视框都被选中。


我们的目标是使用程序重新定位并且旋转剖视框,使其与范围框的位置、方向和尺寸都相同。

Jeremy

你需要实现如下的操作步骤:

1. 从范围框获取所需几何数据

范围框没有提供直接的 Location 属性,所以只能从它的几何定义中计算得到。通过 RevitLookup 我们可以发现,范围框包含12条线段(即范围框的12条边)。所以你需要通过这12条线段来范围框的计算尺寸和方向,进而计算视图剖视框。

2. 创建需要的剖视框对象(转换、最大坐标值、最小坐标值……)

参见 create a section view parallel to a wall

3. 将剖视框对象设置到视图的 SectionBox 属性

view.SectionBox = newSectionBox

为了验证我的方案,我实现了 GetScopeBoxBoundingBox() 方法用于从范围框的12条边中抽取数据创建 BoundingBox 对象,并基于它 创建了 SetSectionBox 命令。

GetScopeBoxBoundingBox() 方法的算法如下:
1. 选取一条边所在线为X轴,一个端点作为原点;
2. 找到其它两条经过原点的线,分别作为Y轴和Z轴;
3. 可以通过Y轴和Z轴的方向选取,确认新的坐标系是右手螺旋方向;


确认坐标系为右手螺旋方向

当且仅当坐标系确定的平行六面体的有符号体积为正值时,该坐标系为右手螺旋方向。有符号体积的计算公式为:前两个坐标轴向量的叉积与第三个坐标轴的点积。

 /// <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 BoxBoundingBoxXYZ bb = new BoundingBoxXYZ();bb.Transform = t;bb.Min = XYZ.Zero;bb.Max = vx + vy + vz;return bb;}

我们还差一点儿就要成功了。

根据范围框计算合适的视图剖视框

现在我需要确认Z轴确实是垂直向上的。在考虑视图方向的前提下,使用最靠近观察者的范围框边界作为剖视框的Z轴。

因此我创建了另外一个方法 GetSectionBoundingBoxFromScopeBox()。它根据范围框的位置、视图方向计算出一个合适的剖视图 Bounding Box:
1. 找到最接近观察者的垂直边界;
2. 使用该边界的底部端点作为原点;
3. 找到另外两条源于原点的边界;
4. 使用这三条边界定义 Bounding Box

使用视图方向和范围框 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 Boxbb = new BoundingBoxXYZ();bb.Transform = t;bb.Min = XYZ.Zero;bb.Max = vx + vy + vz;return bb;}

集成测试

创建一个外部命令,在当前的三维视图中首先找到第一个范围框元素,然后执行如下操作:

1. 访问当前视图并确认是否为三维视图;
2. 选中范围框元素;
3. 使用 GetSectionBoundingBoxFromScopeBox() 方法根据范围框得到剖视框;
4. 将剖视框的 Bounding Box 设置到当前视图的 SectionBox 属性

  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;

结果如下图所示:

表示范围框的虚线被剖视框的实线完全覆盖了。

完整的代码可以在这里下载: SetSectionBox.zip

Building Coder(Revit 二次开发)- 设置匹配范围框的视图剖视框相关推荐

  1. Revit二次开发--设置当前视图为三维视图

    最近在做管道轴侧图,三维视图一直是手动打开,想进一步实现自动话,用代码打开某个系统的三维视图: 主要代码如下: Type type = doc.ActiveView.GetType();if (!ty ...

  2. revit 二次开发之创建图纸和放置视图

    revit中创建图纸指在模型中创建一个图纸视图(以下简称图纸),图纸中可以插入明细表和视图,并且可以打印:放置视图操作指在某个图纸中插入某一个视图. 创建图纸操作需要使用ViewSheet.Creat ...

  3. Revit二次开发——设置圆形风管及管件的尺寸

    对所选中的圆形风管及风管管件设置尺寸参数,主要方法如下: private void ChangeDuctSize(Element element, double diameter){Parameter ...

  4. Revit二次开发之俯视图缩放匹配

    时隔多日偶有所得,来写自己的第二篇博客,和大家共同进步. 应用场景是在Revit二次开发中需要将当前视图切换成三维视图,并且切换成俯视图,从而可以在平面上选点布置族实例.步骤如下: 第一步:找到Rev ...

  5. 【Revit 二次开发 】创建带箭头的文字注释(字体设置+引线箭头设置)

    此篇文章仅是自己的开发经验分享,不具备官方参考价值,如有不足,欢迎批评指正 开发目的: 创建一个带箭头的文字注释 字体为新宋体,大小2.5mm,宽度系数0.7,箭头为30度实心箭头 like this ...

  6. Revit二次开发—载入族并交互式放置

    文章目录 核心代码 问题一:按esc取消放置时报错 问题二:如何在放置一个族实例之后退出放置 问题三:已存在所载入族时,如何正常放置 参考资料 核心代码 using (Transaction tx = ...

  7. Revit二次开发前期准备

    Revit二次开发前期准备 程序是什么:输入-处理-输出 给工程人员带来什么:1.解决重复性的工作2.提供创造性的工作3.互联互通 如何学习编程:跟学,以解决问题为目的,不懂就过,查阅API C#重点 ...

  8. Revit 及 Revit二次开发入门笔记

    加入BIM相关行业后,虽然是个门外汉,但是还是下决心好好钻研,尽快创造些价值. BIM中很关键的软件Revit已下载.我下载的是2017版本,看了几节Revit视频教程,算是有了初步的了解.因为我的工 ...

  9. revit二次开发之程序调试

    欢迎加入BIM行业开发交流1群 群号:711844216(满),二群群号:1016453207 需要Revit二次开发全流程教学 的朋友可以联系我qq:1056295111 一.背景 小伙伴们在rev ...

最新文章

  1. 如何设置TextView textStyle,例如粗体,斜体
  2. java删除页面数据不刷新_Ajax请求数据与删除数据后刷新页面
  3. 阿里云MongoDB,一直被模仿,从未被超越
  4. docker学习之-什么是docker
  5. Python精通-Python列表操作
  6. 背景纹理素材|为前景元素添加焦点
  7. 说一下syslog日志吧~~~
  8. 服务器状态码502什么意思,Http状态码502问题复盘
  9. JS生成验证码、卡密,生成指定位数的字符串
  10. _ASSERTE(_CrtIsValidHeapPointer(block))
  11. python用schedule库实现定时功能,可每天定时运行程序。
  12. MyBatis一对多查询collection三表三层查询
  13. Redis总结 其一 概述 安装 类型
  14. python+yolov3 输出中文标签
  15. Ubuntu 18.04 桌面卡死
  16. oracle db、dba和rdba
  17. 基于初始残差循环神经网络的乳腺癌组织病理学图像的分类
  18. return返回值用法
  19. 搜搜问问做外链是一个误区
  20. HTML常用实体符号

热门文章

  1. 做好APP营销,APP命名的三原则分享
  2. git用命令实现 discard的方法
  3. 普通文件下载 + 前端获取后端返回的文件流并下载
  4. 在任务计划时无法设置账户信息的解决方法
  5. 《思维转变》week-1 1-7 学习的时候应该听音乐吗1-8 如何学习复杂的知识---咖啡店的小把戏
  6. 【听】语言本能,语言进化本质探索
  7. 全程中文:谷歌上线机器学习速成课程
  8. Linux中非常有趣的代码
  9. linux 如何设置待机时间_虚拟机linux系统怎样设置待机时间
  10. web前端学习28-29(综合案例:圣诞节的那些事)