Newly Created Room, Boundaries and Area not being Recognized in Graph

I was hoping someone might be able to help with this issue I’ve been having with my graph (I’m using dynamo for revit 2021). I am trying to get the building area for a building using the approach where a new room is temporarily created external to the building. The process I’m using is:

  1. Collect all the walls in the view.
  2. Filter the walls into vertically and horizontally oriented.
  3. Get the walls that are located the furthest north, east, south and west (4 walls total) and create a bounding box around them.
  4. Scale the bounding box up to two times its size.
  5. Convert the bounding box to a solid. Get the top face of the bounding box solid and create room separation lines on the four edges of that face on the same sketchplane the walls in the view are on.
  6. Place a new room (called “Inverse Building Area”) exterior to the building.

*the next step is the one I’m struggling with
7) Get the newly created room (“Inverse Building Area”) boundary lines and area.

For some reason dynamo/revit recognizes that the new room has been created but I can’t get the room boundaries or the room area within the same graph. The clockwork room.boundaries node just returns an empty list. The room.area node just returns 0 as the area.

It’s weird because I’ve tried splitting the graph into two separate graphs (just creating the room and room separation lines in the first graph, then finding the room area/boundaries in the second graph) and it works.

I thought it may be an issue related to timing, so I’ve tried these approaches so far but without any luck:

  1. Add transaction end/start nodes after the room creation.
  2. Use the waitfor/passthrough node after the room creation.
  3. Use time.delay in a code block to try and wait a few seconds before getting the new list of rooms.

If anyone as any ideas it would be greatly appreciated!

Attached is the dynamo file:
Get Building Area with Bounding Box Method.dyn (189.6 KB)


and the python scripts are here (I numbered them in the screenshot of the graph):

import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)

def WallOrientation(wall):
	loc = wall.Location
	flipped = False
	if hasattr(loc, "Curve"):
		lcurve = loc.Curve
		if hasattr(wall, "Flipped"): flipped = wall.Flipped
		if str(type(lcurve)) == "Autodesk.Revit.DB.Line":
			if flipped: return wall.Orientation.ToVector().Reverse()
			else: return wall.Orientation.ToVector()
		else:
			direction = (lcurve.GetEndPoint(1) - lcurve.GetEndPoint(0)).Normalize()
			if flipped: return XYZ.BasisZ.CrossProduct(direction).ToVector().Reverse()
			else: return XYZ.BasisZ.CrossProduct(direction).ToVector()
	else: return None

walls = UnwrapElement(IN[0])

if isinstance(IN[0], list): OUT = [WallOrientation(x) for x in walls]
else: OUT = WallOrientation(walls)

# Load the Python Standard and DesignScript Libraries
import clr
import sys
clr.AddReference('ProtoGeometry')
clr.AddReference("RevitAPI")
from Autodesk.DesignScript.Geometry import *
clr.AddReference("RevitNodes")
from Autodesk.Revit.DB import *
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference ("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument

# The inputs to this node will be stored as a list in the IN variables.
level_input = UnwrapElement(IN[0])
point_input = UnwrapElement(IN[1])
name = IN[2]
number = IN[3]

# Place your code below this line
TransactionManager.Instance.EnsureInTransaction(doc)

try:
	level_elevation = level_input.Elevation
	
	room_location = UV((point_input.U/275), (point_input.V/275))
	
	room = doc.Create.NewRoom(level_input, room_location)
	room.Name = name
	room.Number = number

	TransactionManager.Instance.TransactionTaskDone()
	
	OUT = room
	
except Exception as e:
	TransactionManager.Instance.TransactionTaskDone()
	OUT = str(e)
import clr
import sys
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager 
from RevitServices.Transactions import TransactionManager 

clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")

import Autodesk 
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *

doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication 
app = uiapp.Application 
uidoc = uiapp.ActiveUIDocument

new_room = IN[0]

all_rooms = FilteredElementCollector(doc)
all_rooms.OfCategory(BuiltInCategory.OST_Rooms)
all_rooms.WhereElementIsNotElementType()
new_list = all_rooms.ToElements()

OUT = new_list

Room boundaries are calculated on an extra thread, after the transaction is completed. As a result you need to ensure the transaction is completed and the extra calculations completed between when you create the room and when you pull the boundaries. My preference for this is to run the graph to create the room and then run the graph to get the boundaries second. That exterior room winds up having a lot of uses in the end.

If you really want them in one graph it is possible, but you will need to take a few steps in between.

Before you start I recommend cleaning up your wall filtering - just use an Element.BoundingBox to get all the wall’s bounding boxes, and then combine those into a single bounding box. No need to sort of filter anything. After that expand the min point on the -x-y axis and the max point on the +x+y axis and you’re done. The min and max point of the expanded BB can be used to generate a rectangle ([minX, maxX,maxX,minX],[minY,minY,maxY,maxY]) and you can generate the room just inside the min or max point.

As noted above you need to wrap up the transactions between the room creation and getting the boundaries. Be sure all other aspects of the graph have completed between the two; reducing the actions proceeding the room creation (see above) will help in that perspective, and ensure everything is passed through the same Transaction.End and Transaction.Start nodes between portions of the graph.

Another option would be to utilize the BuildingEnvelopeAnalyzer class, which I haven’t really looked at yet, but seems like it might perform just as well for your end result. Note that this is in the Analysis portion of the Revit API so additional imports will be required for your Python. BuildingEnvelopeAnalyzer Members

Thank you Jacob! I finally got it to work all in one graph. I think I’ll follow your advice of also having a different set of graphs that do the same process in two steps as well to keep my options open for performing more tasks down the road.

I went through my original graph and cleaned up the filtering and sorting as you had suggested to make sure there were no connections between the different parts of the graph. I also switched back to using the OOTB room.bylocation node instead of the python script I was using.

The thing that got this to work was that after I created the room, I created a new list of rooms and then used the transaction end/ transaction start nodes to pass that new list on to the next portion of the graph.

I have some work to do still to clean this graph up a bit but I will post my final graph here for visibility once I have completed that. Thanks again for your help!

1 Like

Here is the final graph for reference:

Attempt 2 - Get Building Area with Bounding Box Method.dyn (200.5 KB)

Here is the python script, pretty basic just used to collect the rooms:

import clr
import sys
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager 
from RevitServices.Transactions import TransactionManager 

clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")

import Autodesk 
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *

doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication 
app = uiapp.Application 
uidoc = uiapp.ActiveUIDocument

new_room = IN[0]

all_rooms = FilteredElementCollector(doc)
all_rooms.OfCategory(BuiltInCategory.OST_Rooms)
all_rooms.WhereElementIsNotElementType()
new_list = all_rooms.ToElements()

OUT = new_list

The graph itself works to find the building area of a standalone building when in an active plan view. The user just needs to specify the level to create the temporary room/ room separation lines at. Thanks again Jacob for pointing me in the right direction!

1 Like