Collect Eleemnts that are NOT inside any room

Collect Eleemnts that are NOT inside any room

harilalmnCBXDY
Enthusiast Enthusiast
585 Views
3 Replies
Message 1 of 4

Collect Eleemnts that are NOT inside any room

harilalmnCBXDY
Enthusiast
Enthusiast

I have a very huge model with hundreds of rooms. I want to collect elements that DO NOT belong to (or inside) any room. I have tried extracting room solids and check with each element using ElementIntersectsSolidFilter. I know this is extremely slow process. Here is the code;

public async Task<List<ElementId>> CollectElementsOutsideAsync(Phase phase, IProgress<double> progress)
{
    Debris.Clear();

    // Step 1: Collect valid rooms (Main Thread)
    List<Room> validRooms = new FilteredElementCollector(doc)
        .WhereElementIsNotElementType()
        .OfCategory(BuiltInCategory.OST_Rooms)
        .Cast<Room>()
        .Where(r => r.Area > 0 && r.Location != null)
        .ToList();

    List<Solid> roomSolids = new List<Solid>();
    SpatialElementBoundaryOptions sebOptions = new SpatialElementBoundaryOptions
    {
        SpatialElementBoundaryLocation = SpatialElementBoundaryLocation.Center,
        StoreFreeBoundaryFaces = true
    };

    for (int i = 0; i < validRooms.Count; i++)
    {
        Room room = validRooms[i];
        Debug.Print($"Processing Room {i + 1} of {validRooms.Count}");
        try
        {
            SpatialElementGeometryCalculator cal = new SpatialElementGeometryCalculator(doc, sebOptions);
            SpatialElementGeometryResults results = cal.CalculateSpatialElementGeometry(room);
            Solid roomSolid = results.GetGeometry();
            roomSolids.Add(roomSolid);
        }
        catch
        {
            try
            {
                sebOptions.SpatialElementBoundaryLocation = SpatialElementBoundaryLocation.Finish;
                SpatialElementGeometryCalculator cal = new SpatialElementGeometryCalculator(doc, sebOptions);
                SpatialElementGeometryResults results = cal.CalculateSpatialElementGeometry(room);
                Solid roomSolid = results.GetGeometry();
                roomSolids.Add(roomSolid);
            }
            catch (Exception e)
            {
                Debug.Print($"...No solid found!!");
            }
        }

        await Task.Delay(50);

        Debug.Print($"...Done");
        progress.Report((i + 1) / (double)validRooms.Count * 50); // Report progress for room processing
        await Task.Yield(); // Ensure the UI updates the progress bar during the loop
    }

    Debug.Print($"...Done");


    List<ElementId> all3DElements = doc.GetAll3DElements().Select(e => e.Id).ToList();
    List<ElementId> elementsInsideRooms = new List<ElementId>();


    int totalSolids = roomSolids.Count;
    foreach (Solid roomSolid in roomSolids)
    {
        ElementIntersectsSolidFilter elementIntersectsSolidFilter = new ElementIntersectsSolidFilter(roomSolid);
        FilteredElementCollector collector = new FilteredElementCollector(doc, all3DElements);
        ICollection<ElementId> elementsInThisRoom = collector.WherePasses(elementIntersectsSolidFilter).ToElementIds();
        elementsInsideRooms.AddRange(elementsInThisRoom);
        await Task.Delay(50);
        progress.Report(75 + (roomSolids.IndexOf(roomSolid) / (double)totalSolids * 25)); // Report progress for filtering elements
        await Task.Yield();
    }

    progress.Report(100); // Report progress for completion

    return all3DElements.ToList().Where(e => elementsInsideRooms.Contains(e) == false).ToList();
}

 

The line;

 

ElementIntersectsSolidFilter elementIntersectsSolidFilter = new ElementIntersectsSolidFilter(roomSolid);

 

 

throws error saying;
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'

Another question; Is there any better approach than to make Revit do this heavy lift?

0 Likes
Accepted solutions (1)
586 Views
3 Replies
Replies (3)
Message 2 of 4

jeremy_tammik
Alumni
Alumni

Good question. Yes, easy to solve, but tricky to optimise. Also, the answer will always depend on the model, and detailed properties on the elements. Also, what exact elements are you interested in? If you are only focused on family instances, you could use the family instance Room property, optionally specifying a Phase:

  

  

That would probably be tremendously much easier and much more efficient than a spatial analysis.

 

If your model is super well set up, maybe that would be all you need.

 

However, which model ever is? Plus, no rule without exceptions.

 

So, that is my answer to you, and an invitation to provide more details.

 

Next, I will also add an answer from the Gemini LLM that I have been consulting a lot recently and that has provided many satisfying answers. It is much more diligent than I am, so it may help as well. Here is its initial take on this:

 

Gemini:  Alright, let's tackle the challenge of identifying elements outside any room in a large Revit model efficiently. This is a common issue, especially with complex models, and a naive approach can be computationally expensive. We'll explore a few strategies, building from simple to more optimized solutions, leveraging best practices and insights from The Building Coder and other Revit API resources.

Understanding the Problem:

The core issue is efficiently determining if an element's location intersects with any room in the model. Checking against every single room individually can be slow, especially with hundreds of rooms.

Methods and Code Examples:

1. Basic Approach (Inefficient for Large Models):

This is a straightforward approach, but as mentioned, it's not recommended for large models due to performance issues. It iterates through all elements and then checks against all rooms.

C#
 
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System.Collections.Generic;
using System.Linq;

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
    Document doc = commandData.Application.ActiveUIDocument.Document;

    // 1. Get all rooms in the model
    FilteredElementCollector roomCollector = new FilteredElementCollector(doc);
    roomCollector.OfCategory(BuiltInCategory.OST_Rooms);
    List<Element> rooms = roomCollector.ToElements() as List<Element>;

    // 2. Get all elements you want to check (e.g., all family instances)
    FilteredElementCollector elementCollector = new FilteredElementCollector(doc);
    elementCollector.OfClass(typeof(FamilyInstance)); // Or other relevant classes
    List<Element> allElements = elementCollector.ToElements() as List<Element>;

    List<Element> elementsOutsideRooms = new List<Element>();

    foreach (Element element in allElements)
    {
        bool isInsideRoom = false;
        Location elementLocation = element.Location;

        if (elementLocation != null) //Handle elements without a location
        {
            foreach (Element room in rooms)
            {
                Room r = room as Room;
                if (r != null)
                {
                    // Use the Room's IsInside method (more efficient than spatial intersection)
                    if (r.IsInside(elementLocation))
                    {
                        isInsideRoom = true;
                        break; // Exit inner loop once a room is found
                    }
                }

            }
            if (!isInsideRoom)
            {
                elementsOutsideRooms.Add(element);
            }
        }
    }

    // 3. Do something with the elementsOutsideRooms list (e.g., display in a view)
    TaskDialog.TaskDialog.Show("Elements Outside Rooms", $"Found {elementsOutsideRooms.Count} elements outside rooms.");

    return Result.Succeeded;
}

2. Spatial Filtering (More Efficient):

This approach uses the SpatialElementCollector and its IntersectsElement filter, which is considerably faster than the basic method.

C#
 
// ... (Get all rooms as before)

List<ElementId> roomIds = rooms.Select(r => r.Id).ToList();

FilteredElementCollector elementCollector = new FilteredElementCollector(doc);
elementCollector.OfClass(typeof(FamilyInstance));

List<Element> allElements = elementCollector.ToElements() as List<Element>;
List<Element> elementsOutsideRooms = new List<Element>();

foreach (Element element in allElements)
{
    bool isInsideRoom = false;
    Location elementLocation = element.Location;

    if (elementLocation != null)
    {
        // Use SpatialElementCollector to check for intersection with ANY room
        SpatialElementCollector spatialCollector = new SpatialElementCollector(doc, roomIds);
        spatialCollector.IntersectsElement(element); // Check if this element intersects with any of the rooms in roomIds

        if (spatialCollector.Count() > 0)
        {
            isInsideRoom = true;
        }

        if (!isInsideRoom)
        {
            elementsOutsideRooms.Add(element);
        }
    }
}

// ... (Handle elementsOutsideRooms)

3. Bounding Box Intersection Check (Potentially Fastest):

This method uses bounding box intersections as a quick pre-filter. It's the most efficient way to reduce the number of elements to check with IsInside.

C#
 
// ... (Get all rooms as before)

// Create a BoundingBoxXYZ for all rooms (union of room bounding boxes)
BoundingBoxXYZ combinedRoomBB = null;

foreach (Room room in rooms)
{
    if (combinedRoomBB == null)
    {
        combinedRoomBB = room.get_BoundingBox(doc.ActiveView); // Or a 3D view
    }
    else
    {
        combinedRoomBB.Union(room.get_BoundingBox(doc.ActiveView));
    }
}

FilteredElementCollector elementCollector = new FilteredElementCollector(doc);
elementCollector.OfClass(typeof(FamilyInstance));

List<Element> allElements = elementCollector.ToElements() as List<Element>;
List<Element> elementsOutsideRooms = new List<Element>();

foreach (Element element in allElements)
{
    Location elementLocation = element.Location;
    if (elementLocation != null)
    {
        BoundingBoxXYZ elementBB = element.get_BoundingBox(doc.ActiveView); // Or a 3D view

        if (combinedRoomBB != null && elementBB != null && combinedRoomBB.Intersects(elementBB))
        {
            bool isInsideRoom = false;
            foreach (Room room in rooms)
            {
                if (room.IsInside(elementLocation))
                {
                    isInsideRoom = true;
                    break;
                }
            }
            if (!isInsideRoom)
            {
                elementsOutsideRooms.Add(element);
            }
        }
        else
        {
            elementsOutsideRooms.Add(element);
        }

    }
}

// ... (Handle elementsOutsideRooms)

Key Improvements and Explanations:

  • SpatialElementCollector: This collector is specifically designed for spatial queries and is much more efficient than iterating through all elements and rooms individually.
  • Bounding Box Pre-filtering: Checking bounding box intersections is a very fast way to eliminate elements that are definitely outside the room area. We only need to do the more expensive IsInside check for elements whose bounding boxes intersect the combined room bounding box.
  • IsInside Method: The Room.IsInside() method is the recommended way to determine if a point (element location) is within a room. Avoid using geometric intersections directly, as IsInside is optimized for this purpose.
  • Handling Elements without Location: The code now checks for elementLocation != null to avoid exceptions when dealing with elements that don't have a location (e.g., some annotation elements).
  • Combined Bounding Box: Creating a union of all room bounding boxes allows us to efficiently check if an element's bounding box even potentially intersects with any room.

Choosing the Right Method:

  • For very small models, the basic approach might be sufficient.
  • For medium-sized models, the SpatialElementCollector approach is recommended.
  • For large models with many rooms, the bounding box pre-filtering method should provide the best performance.

Remember to test and profile your code with your specific model to determine the most efficient solution in your context. The size and complexity of the model, as well as the number of elements you are checking, will all influence performance. By using these optimized techniques, you should be able to efficiently identify elements outside rooms even in very large Revit models.

    

Jeremy: how about using the FamilyInstance Room property, optionally with a Phase?

    

Gemini: You're absolutely right! The FamilyInstance.Room property is a crucial optimization and often the most efficient approach, especially when combined with phase consideration. My apologies for not highlighting it more prominently in the previous response. Let's rectify that.

Leveraging the FamilyInstance.Room Property:

The FamilyInstance.Room property directly provides the room an instance is located in (or null if it's not in any room). This is significantly faster than any spatial intersection calculations.

Code Example (Most Efficient):

C#
 
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System.Collections.Generic;
using System.Linq;

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
    Document doc = commandData.Application.ActiveUIDocument.Document;

    // 1. Get all Family Instances (or other relevant element types)
    FilteredElementCollector collector = new FilteredElementCollector(doc);
    collector.OfClass(typeof(FamilyInstance)); // Or other relevant classes

    // 2. (Optional) Get the desired phase if needed.  If you want to check against the *current* phase, skip this.
    Phase phase = null;
    // Example: Get the active view's phase
    View activeView = doc.ActiveView;
    if (activeView != null)
    {
        phase = activeView.Phase;
    }
    else
    {
        TaskDialog.TaskDialog.Show("Error", "No Active View. Please activate a view and try again.");
        return Result.Failed;
    }


    List<Element> elementsOutsideRooms = new List<Element>();

    foreach (Element element in collector)
    {
        FamilyInstance instance = element as FamilyInstance;

        if (instance != null)
        {
            Room room = instance.get_Room(phase); // Use the appropriate phase

            if (room == null)
            {
                elementsOutsideRooms.Add(element);
            }
        }
    }

    // 3. Do something with the elementsOutsideRooms list
    TaskDialog.TaskDialog.Show("Elements Outside Rooms", $"Found {elementsOutsideRooms.Count} elements outside rooms.");

    return Result.Succeeded;
}

Key Advantages and Explanations:

  • Direct Room Access: instance.get_Room(phase) directly retrieves the room the instance is in for the specified phase. This eliminates the need for any spatial calculations or intersections.
  • Phase Consideration: The get_Room() method accepts a Phase parameter. This is essential for handling phased models correctly. Elements might be in different rooms (or no room) in different phases. Providing the correct phase ensures you're getting the correct room association for that phase.
  • Performance: This approach is by far the most performant because it avoids all spatial queries. It's a simple property lookup.
  • Handling Null Rooms: The code explicitly checks for room == null to identify instances that are not in any room for the given phase.

Which Method to Use?

  • FamilyInstance.Room (with Phase): This is almost always the best approach. Use it whenever possible. It's the fastest, cleanest, and most reliable.
  • Spatial Filtering: Use this only when you absolutely cannot use the FamilyInstance.Room property (e.g., if you're dealing with elements that don't have a Room property, or if you need more complex spatial relationships).
  • Bounding Box Intersection: Avoid this method if you can use FamilyInstance.Room or SpatialElementCollector. It's only useful as a last resort if you need to optimize spatial queries further and the other methods are not applicable.

Important Note on Phases:

Phasing is a critical aspect of Revit. Always consider the phase when working with room associations. If you omit the phase in instance.get_Room(), you'll get the room association in the current phase of the view. This might not be what you intend, especially if you're processing elements across different phases. Make sure to use the appropriate phase for your analysis.

I sincerely apologize for the oversight in the previous response. The FamilyInstance.Room property is fundamental to this task, and it should have been the primary recommendation. Thank you for pointing it out!

    

 

   

 

 

 

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 3 of 4

harilalmnCBXDY
Enthusiast
Enthusiast

Dear @jeremy_tammik 

Thanks for the quick response..! 
I wish to ask a couple of questions. In my case, I am not targeting only FamilyInstances. It could be, well, any 3d element. Including system families. 

While doing the modelling, people tend to copy-paste stuff (to the project, pasting outside the bounds of the building) from some other file, so that they could select the element and use 'Create Similar' . Later they forget to delete this and these will be around the model as some..."Virtual Construction Debris" LOL..!

I am trying to collect all these. So it is not just FamilyInstance alone. If that was the case, as you suggest always, I could have used IsPointInRoom method. BoundingBox of rooms also might not help, as these are axis aligned ones and not object oriented. The above method was working for me, but it was throwing some memory access error. 


0 Likes
Message 4 of 4

jeremy_tammik
Alumni
Alumni
Accepted solution

Yes. In that case, you will indeed probably need to perform some kind of intersection test for the final perfect result. However, I would try to optimise that as far as possible, maybe in several steps. For a quick initial weeding, you could indeed just use bounding box intersection tests. That should enable you to identify the majority of issues. Then in a second step, you could for instance try to generate one single polyhedron wrapping all the rooms and search for objects located outside of that polyhedron. Another possible option to optimise the analysis might be to project all the rooms and all the objects onto the 2D XY plane, reducing the intersection test from a performance heavy 3D problem to a much lighter 2D one. It depends on your model, of course. You could also try to identify what your users typically do, and what you want to fix. If they always place the Virtual Construction Debris in certain well-defined locations or at a certain minimal distance, that simplifies the task tremendously. Go ahead and ask your questions 🙂

   

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open