We recently discussed filtering for intersecting elements.
Here is a closely related issue with an additional twist that I discussed with Gustav Blom, structural engineer at Rambøll in Norway:
Question: I am implementing an add-in that determines all model elements inside a mass in a linked file MassModel.rvt.
I have used a filtered element collector with an ElementIntersectsSolidFilter
using the solid representation of the mass as argument.
Here is my main filtering code:
Public Sub Run() Dim oWrite1 As IO.StreamWriter oWrite1 = IO.File.CreateText("C:/ . . . /ObjectLocation.txt") ' Collect linked files Dim linkedfilefilter As ElementCategoryFilter _ = New ElementCategoryFilter(BuiltInCategory.OST_RvtLinks) Dim linkedfilecollector As FilteredElementCollector _ = New FilteredElementCollector(m_doc) _ .WherePasses(linkedfilefilter) _ .WhereElementIsNotElementType Dim options As Options = New Options() Dim tx As Transaction = New Transaction(m_doc) tx.Start("Object Locate") If linkedfilecollector.Count > 0 Then For Each linkedfile As RevitLinkInstance In linkedfilecollector If linkedfile.GetLinkDocument.Title.Equals("MassModel.rvt") Then Try ' Find all elements with OBJ-LOCATION-RDK ' Parameter And erase previous values Dim collector As FilteredElementCollector _ = New FilteredElementCollector(m_doc) _ .WhereElementIsNotElementType _ .WhereElementIsViewIndependent For Each el As Element In collector If el.Category IsNot Nothing And el.Parameters.Size > 0 Then Dim ElementParameters As ParameterSet = el.Parameters For Each elparam As Parameter In ElementParameters If elparam.Definition.Name = "OBJ-LOCATION-RDK" Then elparam.Set("") oWrite1.WriteLine(el.Category.Name) End If Next End If Next Dim linkedmassCatFilter As ElementCategoryFilter _ = New ElementCategoryFilter(BuiltInCategory.OST_Mass) Dim massCollector As FilteredElementCollector _ = New FilteredElementCollector(linkedfile.GetLinkDocument) _ .WhereElementIsNotElementType() _ .WherePasses(linkedmassCatFilter) Dim populatedcounter As Integer = 0 For Each mass As Element In massCollector Dim solSet As Generic.IEnumerable(Of Solid) _ = mass.Geometry(options).OfType(Of Solid)() For Each potSol As Solid In solSet If (potSol IsNot Nothing And Not potSol.Edges.IsEmpty) Then Dim elementintersectsolidfilter As ElementIntersectsSolidFilter _ = New ElementIntersectsSolidFilter(potSol) Dim intersectingelems As FilteredElementCollector _ = New FilteredElementCollector(m_doc) _ .WherePasses(elementintersectsolidfilter) oWrite1.WriteLine("intersecting elems: " _ + intersectingelems.Count.ToString) For Each intersectingelem As Element In intersectingelems Dim ElementParameters As ParameterSet _ = intersectingelem.Parameters For Each param As Parameter In ElementParameters If param.Definition.Name = "OBJ-LOCATION-RDK" Then If Not param.AsString = "" Then param.Set(param.AsString + "+" + mass.Name) Else param.Set(mass.Name) populatedcounter += 1 End If End If Next Next End If Next Next MsgBox("Populated parameter OBJ-LOCATION-RDK for " _ + populatedcounter.ToString + " model elements") Exit For Catch ex As Exception End Try Else MsgBox("No linked file by name of MassModel.rvt found") End If Next Else MsgBox("No linked files in project. " _ + "Please link a file named MassModel.rvt into project") End If tx.Commit() oWrite1.Close() End Sub
The strange thing is this: it only returns the elements that have been offset or disjoined from their work plane.
Is there a simpler way to achieve what I am trying to do, like the geometry.DoesIntersect
node in Dynamo?
Answer: I'll begin with a comment or two on your sample code:
First, it is cleaner and safer and easier to encapsulate transactions in a using
clause:
using using
automagically disposes and rolls back.
Refer to The Building Coder topic group for more
on handling transactions and transaction groups.
Secondly, you can access a parameter on an element directly by name.
It is better to use a display name independent method when possible, but it works well if you can guarantee that the given parameter name is unique on that element.
Then you can thus replace the following lines of code:
Dim ElementParameters As ParameterSet = el.Parameters For Each elparam As Parameter In ElementParameters If elparam.Definition.Name = "OBJ-LOCATION-RDK" Then elparam.Set("") oWrite1.WriteLine(el.Category.Name) End If Next
They can be replaced by something like:
IListplist = e.GetParameters("OBJ-LOCATION-RDK") Parameter elparam = plist[0] elparam.Set("")
That would be faster, more efficient, easier to read and understand.
It has nothing to do with your question, though, does it?
Looking closer at your specific question:
I was under the impression that all Dynamo code is public domain and open source, so you can explore the implementation of the DoesIntersect
node yourself.
Have you checked out that possibility?
The mass element lives in linkedfile.GetLinkDocument
, and so do the solids potSol
that you extract from it. Is that correct?
The intersecting elements that you are searching for live in the project document m_doc
, correct?
What is the transformation from the linked document into the project document?
Have you taken account of that?
It is interesting to hear that the intersection works in some cases, but not in others: 'elements offset or disjoined from their work plane'.
Maybe that is affecting their transformation in some way.
I recently discussed filtering for intersecting elements in general.
Another, even more relevant discussion concerns linked file element intersection solid geometry. There, I make the following suggestion:
You have two solids, A
in your main project P
and B
in your linked project Q
.
T
from the linked document Q
coordinates to P
's.Q
and retrieve the solid Sb
of B
.P
's coordinate space: T * Sb
.Sa
of A
.T*Sb
with Sa
.Now you can use an element intersection filter based on the intersection result like in the discussion mentioned before.
The transformation of Sb
can be obtained using
the SolidUtils.CreateTransformed method.
Please check that out as well.
Response: You are correct; I had not considered the transformation of the linked mass elements, and it was only by dumb luck that every time I offset or disjoined a model element it would intersect with the default position of the linked masses.
Once the transform was added to the code it worked just as expected.
You are welcome to share this on your blog.
Here is the relevant code:
' Determine transform of linked file in main project Dim transform As Transform = linkedfile.GetTotalTransform For Each mass As Element In massCollector Dim massSolids As Generic.IEnumerable(Of Solid) _ = mass.Geometry(options).OfType(Of Solid)() For Each massSolid As Solid In massSolids If (massSolid IsNot Nothing _ And Not massSolid.Edges.IsEmpty) Then ' Apply transform to mass Dim transformedSolid As Solid = SolidUtils _ .CreateTransformed(massSolid, transform) Dim elementIntersectsSolidFilter As _ ElementIntersectsSolidFilter = New _ ElementIntersectsSolidFilter(transformedSolid) Dim intersectingElems As FilteredElementCollector _ = New FilteredElementCollector(m_doc) _ .WhereElementIsNotElementType _ .WhereElementIsViewIndependent _ .WherePasses(elementIntersectsSolidFilter) For Each intersectingElem As Element _ In intersectingElems Dim elParams As IList(Of Parameter) _ = intersectingElem.GetParameters( "Mass Intersections") If elParams.Count > 0 Then Dim elParam As Parameter = elParams(0) If elParam.AsString = "" Then elParam.Set(mass.Name) populatedCounter += 1 Else elParam.Set(elParam.AsString _ + "+" + mass.Name) End If End If Next End If Next Next
Apart from cleaning it up a bit, the only changes made from the original case are in the lines beneath the comments.
Many thanks to Gustav for raising this interesting issue and sharing his solution!