Building Coder(Revit 二次开发)- 使用实体相交(Solid Intersection)过滤有接触的梁

原文链接: Filter for Touching Beams Using Solid Intersection

几何创建工具创建的临时实体(Solid)可以用于几何特征过滤器。

问题


我想通过编程方式获取全部有接触的梁,不考虑它们之间的连接状态。用户首先选中一根梁,然后程序自动将所有有递归接触的梁(即级联方式接触)选中。


Jeremy


首先让我们讨论这些梁是处于连接状态的情况:你可以使用 Beam.LocationCurve.ElementsAtJoin 属性获取在一根梁的指定端点连接的所有梁。然后再遍历得到的梁的集合,对每根梁继续使用 Beam.LocationCurve.ElementsAtJoin 属性获取级联模式下新的连接梁的集合。你可以在 SDK 例程 TraverseSystem 中找到类似的处理机制,这个例程展示了在管道系统中如何使用 MEP 连接管理器(Connection Manager)来遍历所有处于连接状态的元素。


然后回到你的需求:这些梁不是处于连接状态,而只是相互接触的。我们可以使用 ElementIntersectsSolidFilter,在一个由实体定义的空间区域中检测所有与该区域相交的元素。这个实体可以来自实际的 BIM 模型,也可以是临时创建的且只存在于内存中。

为了获取所有级联方式接触的梁,我们必须为每根梁都创建一个实体和 ElementIntersectsSolidFilter。如果你需求中的接触是指在任意位置(即不是在梁的两个端点),那么应该按照当前梁的形状创建一个形状拉伸实体,然后检测与该形状拉伸实体有任何空间相交或是足够接近的梁。如果你只关心有端点接触的梁,那可以简单地在当前梁的端点处创建一个球体。

这个操作应该递归地应用到所有找到的梁。当然,你必须将已经处理过的梁排除在外,否则会导致死循环。

以下是我考虑的实现流程:

1. 维护三个梁列表

- 已经处理过的梁(已处理梁表)
- 正在被处理的梁(当前梁表)
- 与正在被处理的梁相邻的梁(相邻梁表)

2. 选中一根梁添加到当前梁表

3. 如果当前梁表非空,则重复如下处理

- 将当前梁添加到已处理梁表
- 清空相邻梁表
- 将与当前梁有接触的所有梁添加到相邻梁表
- 将在上一步中找到的相邻梁表作为新的当前梁表

在下面的代码中,检测与指定梁接触的所有梁的代码在方法 AddConnectedElements() 中。该方法首先在梁的两个端点分别创建一个球体,然后创建一个与基于球体的空间相邻检测器,并将其应用到一个 ElementIntersectsSolidFilter 中。执行过滤器,并从过滤结果中删除已经处理过的梁,和已经找到的相邻梁,然后将最后结果添加到相邻梁表。

  /// 
  /// 获取所有与元素“e”相邻的元素(排除已处理过的元素)
  /// 
  void AddElementsIntersectingSphereAt(
    List neighbours,
    XYZ p,
    List visited,
    Document doc )
  {
    Solid sphere = CreateSphereAt( doc.Application.Create, p, _sphere_radius );
 
    ElementIntersectsSolidFilter intersectSphere = new ElementIntersectsSolidFilter( sphere );
 
    FilteredElementCollector collector = new FilteredElementCollector( doc )
        .WhereElementIsCurveDriven() // 可以处理任意 Location 为 LocationCurve 的元素
        .OfCategory( _bic )
        .Excluding( visited.Union( neighbours ).ToList() )
        .WherePasses( intersectSphere );
 
    neighbours.AddRange( collector.ToElementIds() );
  }
 
  /// 
  /// Determine all neighbouring elements close to 
  /// the two ends of the current element 'e', 
  /// skipping all previously visited ones.
  /// 
  void AddConnectedElements(
    List neighbours,
    Element e,
    List visited )
  {
    Location loc = e.Location;
 
    Debug.Print( string.Format( "current element {0} has location {1}", 
			ElementDescription( e ), 
			null == loc ? "" : loc.GetType().Name ) );
 
    LocationCurve lc = loc as LocationCurve;
 
    if( null != lc )
    {
      Document doc = e.Document;
 
      Curve c = lc.Curve;
 
      XYZ p = c.get_EndPoint( 0 );
      XYZ q = c.get_EndPoint( 1 );
 
      AddElementsIntersectingSphereAt( neighbours, p, visited, doc );
 
      AddElementsIntersectingSphereAt( neighbours, q, visited, doc );
    }
  }

注意相邻梁表和已处理梁表都是 ElementId 集合,而不是 Element 集合。因为 .NET 的比较机制对于 Revit Element 的处理不太靠谱。另外 ElementId 集合正好也符合 
FilteredElementCollector 的 Exclude() 方法的参数要求。

我先尝试针对相邻梁表和已处理梁表调用两次 Exclude() 方法。但是如果第一次的处理结果返回一个空集合,则在空集合上调用 Exclude() 会抛出异常。当然我们可以在每次
调用 Exclude() 之前判断集合是否为空。不过我想更简洁的方法是先将相邻梁表和已处理梁表组合成一个梁表,然后一次性调用 Exclude() 方法。

public Result Execute(
  ExternalCommandData commandData,
  ref string message,
  ElementSet elements )
{
  UIApplication uiapp = commandData.Application;
  UIDocument uidoc = uiapp.ActiveUIDocument;
  Application app = uiapp.Application;
  CreationApp creapp = app.Create;
  Document doc = uidoc.Document;
  Selection sel = uidoc.Selection;
  Reference r = null;
 
  try
  {
    r = sel.PickObject( ObjectType.Element, "Please select a beam" );
  }
  catch( RvtOperationCanceledException )
  {
    return Result.Cancelled;
  }
 
  // 初始梁
  Element start = doc.GetElement( r );
 
  // 当前梁表(我们需要查找它们的相邻梁)
  List current = new List();
 
  current.Add( start.Id );
 
  // 已处理梁表
  List visited = new List();
 
  // 相邻梁表
  List neighbours = new List();
 
  // 递归调用
  while( 0 < current.Count )
  {
    // 记录已处理梁表
    visited.AddRange( current );
 
    neighbours.Clear();
 
    // 查找当前梁表的相邻梁表(未处理)
    foreach( ElementId id in current )
    {
      Element e = doc.GetElement( id );
      AddConnectedElements( neighbours, e, visited );
    }
 
    // 当前梁表处理完毕,找到相邻梁表成为下一次操作的当前梁表
    // newly found become the next current ones
 
    current.Clear();
    current.AddRange( neighbours );
  }
 
  foreach( ElementId id in visited )
  {
    uidoc.Selection.Elements.Add( doc.GetElement( id ) );
  }
  return Result.Succeeded;
}

注意这个解决方案适用于所有基于曲线的元素。可以用于查找所有接触墙体、管线、管道等等场景。


完整的代码可以从这里下载: SelectTouchingBeams.zip

你可能感兴趣的:(Revit)