Revit API: 光线追踪 ReferenceIntersector

前言

作为三维软件的使用者,有的时候是很好奇它们是如何做到选中一个物体的。可以想象一下,电脑屏幕是一个二维的,当我们点击鼠标的时候的,它就像是射出一条光线,然后选中的物体就是和这条光线相交的。
Revit 提供了一个类 ReferenceIntersector,它的功能和上面的描述非常接近,不过它并不局限于从屏幕出发的一条的光线,可以是任意的位置和方向。

ReferenceIntersector

两行代码就可以找到从 center 出发,rayDirection 为方向的射线遇到的第一个构件。实际上,难点其实也不在于这个类怎么用。而是光线的位置如何设定,怎么理解 Revit 的坐标系。

ReferenceIntersector refIntersector = new ReferenceIntersector(view3D);
ReferenceWithContext referenceWithContext = refIntersector.FindNearest(center, rayDirection);

例子

来源:官方文档,有修改,该例子没有 Transaction,无法运行,需要加上。
这个例子的目的是假设有一个天窗(skyLight),从这个天窗向下找到的第一个楼板。
步骤:

  1. 通过 UI 选中天窗
  2. 计算从天窗到楼板的距离,并用一条线表示
  3. 用 ModelCurve 来表示这条线

效果:
在这里插入图片描述

通过 UI 选中天窗

关键逻辑:

  1. 得到选中的物体,revit.Application.ActiveUIDocument.Selection.GetElementIds()
  2. 代码仅仅处理你只选中一个天窗的情况,不要多选;
  3. 天窗的类型是 FamilyInstance,它自身的类型是 BuiltInCategory.OST_Windows,它的 host 必须是 BuiltInCategory.OST_Roofs

代码:

Document doc = revit.Application.ActiveUIDocument.Document;

ICollection<ElementId> selectedIds = revit.Application.ActiveUIDocument.Selection.GetElementIds();

// If skylight is selected, process it.
FamilyInstance skylight = null;
if (selectedIds.Count == 1)
{
   foreach (ElementId id in selectedIds)
   {
      Element e = doc.GetElement(id);
      if (e is FamilyInstance)
      {
         FamilyInstance instance = e as FamilyInstance;
         bool isWindow = (instance.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Windows);
         bool isHostedByRoof = (instance.Host.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Roofs);

         if (isWindow && isHostedByRoof)
         {
            skylight = instance;
         }
      }
   }
}

if (skylight == null)
{
   message = "Please select one skylight.";
   return Result.Cancelled;
}

计算从天窗到楼板的距离,并用一条线表示

这个逻辑被包在函数 CalculateLineAboveFloor 里。

第一步,找到第一个不是模板的三维视图。

// Find a 3D view to use for the ReferenceIntersector constructor
FilteredElementCollector collector = new FilteredElementCollector(doc);
Func<View3D, bool> isNotTemplate = v3 => !(v3.IsTemplate);
View3D view3D = collector.OfClass(typeof(View3D)).Cast<View3D>().First<View3D>(isNotTemplate);

第二步,计算光线的方向和位置。起点是天窗几何位置的中心,方向是垂直向下。

BoundingBoxXYZ box = skylight.get_BoundingBox(view3D);
XYZ center = box.Min.Add(box.Max).Multiply(0.5);
XYZ rayDirection = new XYZ(0, 0, -1);

第三步,设置过滤,只要楼板。

ElementClassFilter filter = new ElementClassFilter(typeof(Floor));

第四步,开始进行光线追踪,找到最近的楼板的面。

ReferenceIntersector refIntersector = new ReferenceIntersector(filter, FindReferenceTarget.Face, view3D);
ReferenceWithContext referenceWithContext = refIntersector.FindNearest(center, rayDirection);

第五步,从光线追踪的结果里拿到相交的位置,和光线的起点做一条线。

Reference reference = referenceWithContext.GetReference();
XYZ intersection = reference.GlobalPoint;
Line result = Line.CreateBound(center, intersection);

用 ModelCurve 来表示这条线

步骤:
第一步,创建 SketchPlane

Plane plane = Plane.CreateByNormalAndOrigin(new XYZ(1, 0, 0), line.GetEndPoint(0));
SketchPlane sketchPlane = SketchPlane.Create(doc, plane);

第二步,创建模型线。

ModelCurve curve = doc.Create.NewModelCurve(line, sketchPlane);

整体代码:

public class RayProjection : IExternalCommand
{
    public Result Execute(ExternalCommandData revit, ref string message, ElementSet elements)
    {
        Document doc = revit.Application.ActiveUIDocument.Document;

        ICollection<ElementId> selectedIds = revit.Application.ActiveUIDocument.Selection.GetElementIds();

        // If skylight is selected, process it.
        FamilyInstance skylight = null;
        if (selectedIds.Count == 1)
        {
            foreach (ElementId id in selectedIds)
            {
                Element e = doc.GetElement(id);
                if (e is FamilyInstance)
                {
                    FamilyInstance instance = e as FamilyInstance;
                    bool isWindow = (instance.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Windows);
                    bool isHostedByRoof = (instance.Host.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Roofs);

                    if (isWindow && isHostedByRoof)
                    {
                        skylight = instance;
                    }
                }
            }
        }

        if (skylight == null)
        {
            message = "Please select one skylight.";
            return Result.Cancelled;
        }

        Transaction tran = new Transaction(doc, "Projection");
        tran.Start();

        // Calculate the height
        Line line = CalculateLineAboveFloor(doc, skylight);

        // Create a model curve to show the distance
        Plane plane = Plane.CreateByNormalAndOrigin(new XYZ(1, 0, 0), line.GetEndPoint(0));
        SketchPlane sketchPlane = SketchPlane.Create(doc, plane);

        ModelCurve curve = doc.Create.NewModelCurve(line, sketchPlane);
        tran.Commit();

        // Show a message with the length value
        TaskDialog.Show("Distance", "Distance to floor: " + String.Format("{0:f2}", line.Length));

        return Result.Succeeded;
    }

    /// <summary>
    /// Determines the line segment that connects the skylight to the nearest floor.
    /// </summary>
    /// <returns>The line segment.</returns>
    private Line CalculateLineAboveFloor(Document doc, FamilyInstance skylight)
    {
        // Find a 3D view to use for the ReferenceIntersector constructor
        FilteredElementCollector collector = new FilteredElementCollector(doc);
        Func<View3D, bool> isNotTemplate = v3 => !(v3.IsTemplate);
        View3D view3D = collector.OfClass(typeof(View3D)).Cast<View3D>().First<View3D>(isNotTemplate);

        // Use the center of the skylight bounding box as the start point.
        BoundingBoxXYZ box = skylight.get_BoundingBox(view3D);
        XYZ center = box.Min.Add(box.Max).Multiply(0.5);

        // Project in the negative Z direction down to the floor.
        XYZ rayDirection = new XYZ(0, 0, -1);

        ElementClassFilter filter = new ElementClassFilter(typeof(Floor));

        ReferenceIntersector refIntersector = new ReferenceIntersector(filter, FindReferenceTarget.Face, view3D);
        ReferenceWithContext referenceWithContext = refIntersector.FindNearest(center, rayDirection);

        Reference reference = referenceWithContext.GetReference();
        XYZ intersection = reference.GlobalPoint;

        // Create line segment from the start point and intersection point.
        Line result = Line.CreateBound(center, intersection);
        return result;
    }
}

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客BIM工作室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值