Installer, List of Failures, Adjacent Rooms and Walls

We discuss enhancements to RevitLookup, a list of all built-in Revit failures, and a neat utility to determine all room-wall adjacencies:

Wall adjacent rooms

Adjacent Rooms and Walls

The Revit API discussion forum question on how to extract the names of the rooms separated by a wall prompted Richard RPThomas108 Thomas to share a neat utility method implementing much more than that:

Question: I use Revit to model my projects, then I export the BOMs to a spreadsheet that creates my quotes for clients. However, I am running into a problem:

When I need to demolish or create a wall, I would like to see its location, if any, appear in the BOM.

For example, if I break a wall between the kitchen and the living room, I would like to see "kitchen" or "living room", or even both, next to the surfaces. But I can't find a way to implement this.

Do you have any ideas to help me?

Answer: Via the API, you can use the Room.GetBoundarySegments method.

This will return a nested list of BoundarySegment objects on which you can query their ElementId or LinkElementId property.

Then it is just a case or matching up the ElementId with the wall id and pairing that with the room(s).

Some walls will be part of more than two rooms:

Private Function Obj_210629a( _
  ByVal commandData As Autodesk.Revit.UI.ExternalCommandData, _
  ByRef message As String, _
  ByVal elements As Autodesk.Revit.DB.ElementSet) _
  As Result

  Dim UIDoc As UIDocument = commandData.Application.ActiveUIDocument
  If UIDoc Is Nothing Then Return Result.Cancelled Else
  Dim IntDoc As Document = UIDoc.Document

  Dim FEC As New FilteredElementCollector(IntDoc)
  Dim ECF As New ElementCategoryFilter(BuiltInCategory.OST_Rooms)
  Dim Els As List(Of ElementId) = FEC.WherePasses(ECF) _
    .WhereElementIsNotElementType.ToElementIds

  Dim WallRefs As New Dictionary(Of ElementId, List(Of String))
  For i = 0 To Els.Count - 1
    Dim Rm As Room = IntDoc.GetElement(Els(i))

    Dim BSegs As IList(Of IList(Of BoundarySegment)) = _
      Rm.GetBoundarySegments(New SpatialElementBoundaryOptions)

    For x = 0 To BSegs.Count - 1
      Dim BSegs0 As IList(Of BoundarySegment) = BSegs(x)
      For y = 0 To BSegs0.Count - 1
        Dim Bseg As BoundarySegment = BSegs0(y)
        If Bseg.ElementId <> ElementId.InvalidElementId Then
          Dim CurRef As List(Of String)
          If WallRefs.ContainsKey(Bseg.ElementId) Then
            CurRef = WallRefs(Bseg.ElementId)
          Else
            CurRef = New List(Of String)
            WallRefs.Add(Bseg.ElementId, CurRef)
          End If

          Dim Rm_el As Element = Rm 
          Dim RoomName As String = Rm_el.Name
          If CurRef.Contains(RoomName) = False Then
            CurRef.Add(RoomName)
          End If
        End If
      Next
    Next
  Next

  Dim FECw As New FilteredElementCollector(IntDoc)
  Dim ECFw As New ElementClassFilter(GetType(Wall))
  Dim Elsw As List(Of ElementId) = FECw.WherePasses(ECFw).ToElementIds

  Using Tx As New Transaction(IntDoc, "Name walls based on rooms")
    If Tx.Start = TransactionStatus.Started Then

      For i = 0 To Elsw.Count - 1
        'Some boundary segments will not relate to walls.
        If WallRefs.ContainsKey(Elsw(i)) Then

          Dim El As Element = IntDoc.GetElement(Elsw(i))

          'Using instance comments parameter for convenience

          Dim P As Parameter = El.Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS)
          If P Is Nothing Then Continue For Else

          Dim Refs As List(Of String) = WallRefs(Elsw(i))
          Refs.Sort()
          Dim V As New Text.StringBuilder
          For j = 0 To Refs.Count - 1
            If j = Refs.Count - 1 Then
              V.Append(Refs(j))
            Else
              V.Append(Refs(j) & " / ")
            End If
          Next
          P.Set(V.ToString)

        End If
      Next
      Tx.Commit()
    End If
  End Using
  Return Result.Succeeded
End Function

Thanks to Richard for sharing this very nice VB.NET implementation!

Spelling it out, this does two things:

I ported the VB.NET code to C# and The Building Coder samples:

/// <summary>
/// For all rooms, determine all adjacent walls,
/// create dictionary mapping walls to adjacent rooms,
/// and tag the walls with the adjacent room names.
/// </summary>
void TagWallsWithAdjacentRooms( Document doc )
{
  FilteredElementCollector rooms
    = new FilteredElementCollector( doc )
      .WhereElementIsNotElementType()
      .OfCategory( BuiltInCategory.OST_Rooms );

  Dictionary<ElementId, List<string>> map_wall_to_rooms
    = new Dictionary<ElementId, List<string>>();

  SpatialElementBoundaryOptions opts
    = new SpatialElementBoundaryOptions();

  foreach( Room room in rooms )
  {
    IList<IList<BoundarySegment>> loops 
      = room.GetBoundarySegments( opts );

    foreach (IList<BoundarySegment> loop in loops )
    {
      foreach( BoundarySegment seg in loop )
      {
        ElementId idWall = seg.ElementId;

        if( ElementId.InvalidElementId != idWall )
        {
          if(!map_wall_to_rooms.ContainsKey(idWall))
          {
            map_wall_to_rooms.Add( 
              idWall, new List<string>() );
          }

          string room_name = room.Name;

          if(!map_wall_to_rooms[idWall].Contains( room_name ) )
          {
            map_wall_to_rooms[ idWall ].Add( room_name );
          }
        }
      }
    }
  }

  using( Transaction tx = new Transaction( doc ) )
  {
    tx.Start( "Add list of adjacent rooms to wall comments" );

    Dictionary<ElementId, List<string>>.KeyCollection ids
      = map_wall_to_rooms.Keys;

    foreach( ElementId id in ids )
    {
      Element wall = doc.GetElement( id );

      Parameter p = wall.get_Parameter(
        BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS );

      ifnull != p )
      {
        string s = string.Join( " / ", 
          map_wall_to_rooms[ id ] );

        p.Set( s );
      }
    }
    tx.Commit();
  }
}

Note that the C# version processes all elements whose element id appears as a key in the wall to room mapping, regardless of whether they are in fact a wall or not.

The VB.NET version uses a filtered element collector to retrieve only wall elements.

You need to decide which approach better matches your specific requirements.

This is yet another example of a relationship inversion: every room maintains a relationship to its bounding elements, the walls. by retrieving and processing that mapping, we can invert the relationship and use that to add information to each wall about its adjacent rooms.

This is a common Revit API task. A similar relationship inverter was the topic of one of The Building Coder's very first posts, #16, in October 2008.

List of All Built-In Failures

Gábor Schnierer very kindly shares a list of all built-in failures in the Revit API discussion forum thread on how to get all warnings:

Using the awesome code from @FAIR59 and @perry.swoboda, here is a spreadsheet containing the BuiltInFailures for Revit 2022 with their Severity, Classname, Guid and Description. Might come handy.

Thank you very much, Gábor!

Recent RevitLookup Updates

The number of pull requests to add enhancements to RevitLookup has increased recently significantly.

That is great news!

Each individual improvement may be small and simple. However, they all add up, and the entire community ends up enjoying a brilliant and full-fledged tool.

Here are the important enhancements made since the previous bunch of updates.

Many thanks to all contributors for your great support!

RevitLookup Installation

Luiz Henrique @ricaun Cassettari created the RevitLookup.Installation project, a simple installation using Inno Setup to extract the files to the ApplicationPlugins folder.

It generates a digitally signed version of RevitLookup and includes multi-version support for the Revit releases 2017, 2018, 2019, 2020, 2021 and 2022.

It can obviously also be used as a starting point for your own add-in installer.

Many thanks to Luiz Henrique for this and his other nice contributions!