幾何創建工具創建的臨時實體(Solid)可以用於幾何特征過濾器。
問題
我想通過編程方式獲取全部有接觸的梁,不考慮它們之間的連接狀態。用戶首先選中一根梁,然後程序自動將所有有遞歸接觸的梁(即級聯方式接觸)選中。
Jeremy
首先讓我們討論這些梁是處於連接狀態的情況:你可以使用 Beam.LocationCurve.ElementsAtJoin 屬性獲取在一根梁的指定端點連接的所有梁。然後再遍歷得到的梁的集合,對每根梁繼續使用 Beam.LocationCurve.ElementsAtJoin 屬性獲取級聯模式下新的連接梁的集合。你可以在 SDK 例程 TraverseSystem 中找到類似的處理機制,這個例程展示了在管道系統中如何使用 MEP 連接管理器(Connection Manager)來遍歷所有處於連接狀態的元素。
然後回到你的需求:這些梁不是處於連接狀態,而只是相互接觸的。我們可以使用 ElementIntersectsSolidFilter,在一個由實體定義的空間區域中檢測所有與該區域相交的元素。這個實體可以來自實際的 BIM 模型,也可以是臨時創建的且只存在於內存中。
為了獲取所有級聯方式接觸的梁,我們必須為每根梁都創建一個實體和 ElementIntersectsSolidFilter。如果你需求中的接觸是指在任意位置(即不是在梁的兩個端點),那麼應該按照當前梁的形狀創建一個形狀拉伸實體,然後檢測與該形狀拉伸實體有任何空間相交或是足夠接近的梁。如果你只關心有端點接觸的梁,那可以簡單地在當前梁的端點處創建一個球體。
這個操作應該遞歸地應用到所有找到的梁。當然,你必須將已經處理過的梁排除在外,否則會導致死循環。
以下是我考慮的實現流程:
1. 維護三個梁列表
- 已經處理過的梁(已處理梁表)
- 正在被處理的梁(當前梁表)
- 與正在被處理的梁相鄰的梁(相鄰梁表)
2. 選中一根梁添加到當前梁表
3. 如果當前梁表非空,則重復如下處理
- 將當前梁添加到已處理梁表
- 清空相鄰梁表
- 將與當前梁有接觸的所有梁添加到相鄰梁表
- 將在上一步中找到的相鄰梁表作為新的當前梁表
在下面的代碼中,檢測與指定梁接觸的所有梁的代碼在方法 AddConnectedElements() 中。該方法首先在梁的兩個端點分別創建一個球體,然後創建一個與基於球體的空間相鄰檢測器,並將其應用到一個 ElementIntersectsSolidFilter 中。執行過濾器,並從過濾結果中刪除已經處理過的梁,和已經找到的相鄰梁,然後將最後結果添加到相鄰梁表。
[csharp]
/// <summary>
/// 獲取所有與元素“e”相鄰的元素(排除已處理過的元素)
/// </summary>
void AddElementsIntersectingSphereAt(
List<ElementId> neighbours,
XYZ p,
List<ElementId> 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<ElementId>( neighbours ).ToList<ElementId>() )
.WherePasses( intersectSphere );
neighbours.AddRange( collector.ToElementIds() );
}
/// <summary>
/// Determine all neighbouring elements close to
/// the two ends of the current element 'e',
/// skipping all previously visited ones.
/// </summary>
void AddConnectedElements(
List<ElementId> neighbours,
Element e,
List<ElementId> visited )
{
Location loc = e.Location;
Debug.Print( string.Format( "current element {0} has location {1}",
ElementDescription( e ),
null == loc ? "<null>" : 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() 方法。
[csharp]
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<ElementId> current = new List<ElementId>();
current.Add( start.Id );
// 已處理梁表
List<ElementId> visited = new List<ElementId>();
// 相鄰梁表
List<ElementId> neighbours = new List<ElementId>();
// 遞歸調用
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;
}