Using Intersection Filter with Linked File

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:

Rambøll

Determining Elements Intersecting Mass in a Linked File

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?

Coding Suggestions and Transformations

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:

  IList plist = 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.

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.

Solution by Applying Transformations

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.

Rambøll

Many thanks to Gustav for raising this interesting issue and sharing his solution!