Room performance work-around – Export/Delete & Import

Are rooms making it too slow to modify walls in your Revit model? An advanced Revit user recently contacted me to ask what could be done about it taking 10 seconds to move a wall because of Revit’s room calculations.

This demonstration shows how data about all rooms in the model can saved to file and the rooms deleted so that you can work without the room-associated performance penalty. Then when you want the rooms back, run the Import command to re-create the rooms based on the data that was saved to file.

Using BoundingBoxContainsPointFilter with Rooms & Filled Regions

One of the Revit users who I have been teaching how to use the Revit API had this question:

“I want to renumber my rooms automatically in a floor plan view by getting the room’s location point and seeing if it falls within the boundary of a filled region. I would have a grid of filled regions overlaid on the plan, each with a unique comment, such as ‘A1’, ‘A2’, ‘B1’, etc. The code would write the comments value into the room’s number parameter.

My firm’s standard for room naming is to overlay a grid on top of the plan and number each room based on the coordinates of the grid (plus the level number in front). So a person has to do this manually which takes a while. My idea was to put the info into the filled regions (which when grouped could be reused over and over) and number the rooms automatically based on the room location point.”

We solved this as follows. The BoundingBoxContainsFilter is used to find the filled region that is on top of each room:

public void NumberRoomByRegion()
{
    Document doc = this.ActiveUIDocument.Document;

    using (Transaction t = new Transaction(doc,"Set Rooms By Region"))
    {
        t.Start();
        foreach (Room r in new FilteredElementCollector(doc).OfClass(typeof(SpatialElement)).OfCategory(BuiltInCategory.OST_Rooms).Cast<Room>().Where(q => q.Area > 0))
        {
            XYZ roomPoint = ((LocationPoint)r.Location).Point;
            IList<Element> overlapList = new FilteredElementCollector(doc,doc.ActiveView.Id).OfClass(typeof(FilledRegion)).WherePasses(new BoundingBoxContainsPointFilter(roomPoint)).ToList();
            if (overlapList.Count > 1)
            {
                TaskDialog.Show("Error","Skipping room " + r.Name + ". Overlaps with more than one filled region.");
                continue;
            }
            else if (overlapList.Count == 0)
            {
                continue;
            }
            else
            {
                Element e = overlapList.First();
                r.Number = e.get_Parameter("Comments").AsString();
            }
        }
        t.Commit();
    }
}

Macro to quickly fix the To/From Room parameter for doors

Steve, the wizard of revitoped.blogspot.com, suggested:

It would be cool to use the API to allow a user to pick a door and room to resolve the to/from settings. A user was lamenting the labor hassle of fixing to/from settings because people are mirroring and copying doors which doesn’t necessarily inherit the correct room ownership.

This macro works as follows

  • Select a door or door tag
  • Select the To Room (either the Room or the Room Tag)
  • Repeat as desired, press the ESC key when done
public void FlipRoom()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;
    FamilyInstance door = null;
    string doorMark = "";
    while (true) // create an infinite loop that will be ended by the "return" statement in the catch block
    {
        try
        {
            Element element = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select a door or door tag"));

            if (element is FamilyInstance) // if door is selected
                door = element as FamilyInstance;
            else if (element is IndependentTag) // if door tag is selected
            {
                IndependentTag tag = element as IndependentTag;
                door = doc.GetElement(tag.TaggedLocalElementId) as FamilyInstance;
            }
            doorMark = door.get_Parameter("Mark").AsString();
        }
        catch
        {
            return; // end the command when the user presses ESC when prompted for a door
        }

        // after the user has selected a door, prompt for selection of a room
        Element e = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select To Room for door " + doorMark));

        // Get the room element from the selected object
        Room room = null;
        if (e is RoomTag)
        {
            RoomTag rt = e as RoomTag;
            room = rt.Room;
        }
        else if (e is Room)
        {
            room = e as Room;
        }

        if (room != null && door.ToRoom.Id.IntegerValue != room.Id.IntegerValue) // if the selected room is not already the To Room
        {
            using (Transaction t = new Transaction(doc,"Door " + doorMark + " to room = " + room.Number))
            {
                t.Start();
                door.FlipFromToRoom(); // Flip the settings of "From Room" and "To Room" for the door 
                t.Commit();
            }
        }
    }
}

How to create a room and tag it

An AUGI user asked

“Is there a way to get the UV point from a user clicking the floor plan view during an active API session?

My goal is provide the user with a list of rooms to place and have them select a room from the list in form, click a point on the floor plan view and create the room via the API.”

Here is a macro to create a room with a room tag

public void CreateRoom()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;

    // Get the level of the plan view
    ViewPlan view = doc.ActiveView as ViewPlan;
    Level level = view.GenLevel; // use GenLevel, not Level

    // Create a UV from a point selected by the user (the Z value is not needed)
    XYZ xyz = uidoc.Selection.PickPoint("Select a point");
    UV uv = new UV(xyz.X, xyz.Y);

    using (Transaction t = new Transaction(doc, "Create Room"))
    {
        t.Start();
        Room room = doc.Create.NewRoom(level, uv);
        RoomTag tag = doc.Create.NewRoomTag(room,uv,view);
        t.Commit();
    }
}

Find a Room’s Ceiling Height

Revit can compute some great info about Rooms – area, perimeter, and volume. But out-of-the-box Revit can’t tell you the height of the ceiling above a room. Fortunately the API lets us find this info. Pulling together some things that have been covered in previous posts, here is the code and a screenshot.

public void RoomCeilingHeight()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = new UIDocument(doc);

    // Prompt user to select a room
    Room room = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as Room;

    // Get the location point of the room.
    // If you want to check other points for a room with non-uniform ceiling height
    // use Room.IsPointInRoom to make sure the points are in the room
    LocationPoint roomPoint = room.Location as LocationPoint;

    // The 3 inputs for ReferenceIntersector are:
    // A filter specifying that only ceilings will be reported
    // A FindReferenceTarget option indicating what types of objects to report
    // A 3d view (see http://wp.me/p2X0gy-2p for more info on how this works)
    ReferenceIntersector intersector = new ReferenceIntersector(
        new ElementCategoryFilter(BuiltInCategory.OST_Ceilings),
        FindReferenceTarget.All,
        (from v in new FilteredElementCollector(doc).OfClass(typeof(View3D)).Cast<View3D>() where v.IsTemplate == false && v.IsPerspective == false select v).First());

    // FindNearest finds the first item hit by the ray
    // XYZ.BasisZ shoots the ray "up"
    ReferenceWithContext rwC = intersector.FindNearest(roomPoint.Point, XYZ.BasisZ);

    // Report the data to the user. This information could also be used to set a "Ceiling Height" instance parameter
    if (rwC == null)
        TaskDialog.Show("Height", "no ceiling found");
    else
        TaskDialog.Show("Height",rwC.Proximity.ToString());
}
roomCeilingHeight

The elements that bound a room

To continue this example, I’ve consolidated 3 lines from the previous post into one line below to get the room object from the user’s selection.

Then I get the BoundarySegment elements that define the room’s boundary. To do this I need to create a SpatialElementBoundaryOptions to specify that I want to get the segments at the finish faces of the bounding elements (the other option would to get the centerline segments).

Room.GetBoundarySegments returns a list of lists which is why the nested foreach loops are needed to get the individual segments. The list of lists makes sense if you think about a room like this, where each loop of segments would be in its own list.

loop

After the individual segments are found, I cast each one from an element to a wall and get its LocationCurve. If this were an element placed in Revit at a point (like a desk) then the location would be a LocationPoint. For elements like Walls and Beams which are curve-driven, the Location is a LocationCurve.

The LocationCurve has a Curve property that returns the geometry of the LocationCurve. This curve is what can be used to find the length of the wall.

            Room room = document.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as Room;
            SpatialElementBoundaryOptions options = new SpatialElementBoundaryOptions();
            options.SpatialElementBoundaryLocation = SpatialElementBoundaryLocation.Finish;
            string roomElementInfo = "";
            foreach (IList<Autodesk.Revit.DB.BoundarySegment> boundSegList in room.GetBoundarySegments(options))
            {
                foreach (Autodesk.Revit.DB.BoundarySegment boundSeg in boundSegList)
                {
                     Element e = boundSeg.Element;
                     Wall wall = e as Wall;
                     LocationCurve locationCurve = wall.Location as LocationCurve;
                     Curve curve = locationCurve.Curve;
                     roomElementInfo += e.Name + " " + curve.Length + "\n";
                }
            }
            TaskDialog.Show("Boundary Segment Elements", roomElementInfo);
roomElementsLengths

The screenshot shows the precision of the Revit model and that apparently one of these walls isn’t exactly 10 feet long. If you were presenting this info to the user you might want to use the Math.Round function to clean this up.

Getting info about rooms

A friendly member of the new BoostYourBIM fan club wrote with some suggestions that we might be able to some interesting things with rooms.

In the last example a basic filter was used to get all the doors. The other main way to get elements is with selection, which I will use here.

There are these two “using” statements to add for this code:

using Autodesk.Revit.DB.Architecture;
using Autodesk.Revit.UI.Selection;

And here is the code to select a room and show some basic information about it.

        public void RoomElements()
        {
            Document document = this.ActiveUIDocument.Document;
            UIDocument uidoc = new UIDocument(document);
            Reference reference = uidoc.Selection.PickObject(ObjectType.Element);
            Element element = document.GetElement(reference);
            Room room = element as Room;
            TaskDialog.Show("Room Data",room.Name + "\n" + room.Number);
        }

The UIDocument is a class that handles the “UI”, as opposed to the database (DB) of Revit. This primarily involves views and selection. The constructor creates a UIDocument from a DB Document.

The Reference class is used to reference elements in the documents. A popular thing to do with it is to get the element that it refers to, which is done on the following line.

Then the element is converted into a room using the “as” operator to do the cast conversion.

Finally, a Task Dialog is used to display the room name and number. As the image below shows, a “quirk” of the API is that room.Name includes the room name and number.

selectedRoom