Get Area parameter and populate it to room Parameter

Hi all, I am trying to get Room that is inside the Area(rentable) to fill the Parameter of area"Color" to Room

# Add this import at the beginning of your script
from Autodesk.Revit.DB import ElementSet
from pyrevit import revit, DB

def get_rooms():
    return DB.FilteredElementCollector(revit.doc).OfCategory(DB.BuiltInCategory.OST_Rooms).ToElements()

def get_area():
    return DB.FilteredElementCollector(revit.doc).OfCategory(DB.BuiltInCategory.OST_Areas).ToElements()

def set_room_department(room, color_parameter_value):
    room.Parameter[DB.BuiltInParameter.ROOM_DEPARTMENT].Set(color_parameter_value)

def process_views():
    views = DB.FilteredElementCollector(revit.doc).OfClass(DB.View).ToElements()

    for view in views:
        # Execute the main logic for each view
        main()


def main():
    rooms = get_rooms()
    areas = get_area()

    for area in areas:
        # Access the Color parameter
        color_parameter = area.LookupParameter("Color")

        if color_parameter is not None:
            # Get the color information
            color_parameter_value = color_parameter.AsString()  # Assuming Color is an integer parameter

            for room in rooms:
                room_location = room.Location.Point

                # Check if the area has a valid bounding box
                area_bounding_box = area.get_BoundingBox(revit.doc.ActiveView)
                if area_bounding_box is not None:
                    # Check if the room location is inside the bounding box of the associated area plan
                    if area_bounding_box.IsInside(room_location):
                        # Set the department parameter of the room with the color information
                        set_room_department(room, color_parameter_value)


if __name__ == "__main__":
    process_views()

Resolved it

# Add this import at the beginning of your script
from Autodesk.Revit.DB import ElementSet
from pyrevit import revit, DB
from Autodesk.Revit.DB import Transaction

def get_rooms():
    return DB.FilteredElementCollector(revit.doc).OfCategory(DB.BuiltInCategory.OST_Rooms).ToElements()

def get_areas():
    return DB.FilteredElementCollector(revit.doc).OfCategory(DB.BuiltInCategory.OST_Areas).ToElements()

def set_room_department(room, color_parameter_value):
    # Start a new transaction
    transaction = Transaction(revit.doc, "Set Room Department")
    transaction.Start()

    try:
        value = color_parameter_value
        value_as_string = str(value)
        room.LookupParameter("Department").Set(value_as_string)

        # Commit the transaction if everything is successful
        transaction.Commit()
    except Exception as e:
        # Rollback the transaction if an exception occurs
        transaction.RollBack()
        raise e
      



        

def process_views():
    views = DB.FilteredElementCollector(revit.doc).OfClass(DB.View).ToElements()

    for view in views:
        # Execute the main logic for each view
        main()


def main():
    rooms = get_rooms()
    areas = get_areas()

    for area in areas:
        color_parameter = area.LookupParameter("Color")

        if color_parameter is not None:
            color_parameter_value = color_parameter.AsString()
            
            for room in rooms:
                room_location = room.Location.Point
                
                area_bounding_box = area.get_BoundingBox(revit.doc.ActiveView)
                
                if area_bounding_box is not None:
                    # Check if the room location is inside the bounding box of the associated area plan
                    if (area_bounding_box.Min.X <= room_location.X <= area_bounding_box.Max.X and
                        area_bounding_box.Min.Y <= room_location.Y <= area_bounding_box.Max.Y):
                        # Set the department parameter of the room with the color information
                        set_room_department(room, color_parameter_value)


if __name__ == "__main__":
    process_views()

Hi @saurabhsjha,
I’m glad you found out the problem.
I suggest you to take advantage of the pyrevit library functions and classes:

  • ger_elements_by_categories([category], doc=doc) is a shortcut for the FilteredElementCollector(doc).OfCategory(category).WhereElementIsNotElementType().ToElements()
  • get_elements_by_class is the shortcut for… well, you guessed it :wink:
  • pyrevit.revit.Transaction class is a context manager that handles the commit or rollback for you:
with Transaction(doc):
    value = color_parameter_value
    value_as_string = str(value)
    room.LookupParameter("Department").Set(value_as_string)

Also, it might be better to open only one transaction outside of the loop to avoid a potentially very long undo history! I would put the with Transaction inside the process_views just before the loop.

I see that you’re always using the ActiveView to get the bounding box, but shouldn’t you use the view which are you looping on (by passing it as parameters to the main function)?
On that note, I see from the revit api that the FilterElementCollector can also take the view Id to limit the search to the elements you need, it might remove the need to check id the bounding box is none. Of course, if you go that way you can’t use the first shortcut function I mentioned because it doesn’t support the view id parameter (yet).

1 Like

Thanks @sanzoghenzo thank you for the Suggestion you are correct i was going on Active view in each process. One thing that I was not able to get is this function get_elements_by_categories get_elements_by_categories([DB.BuiltInCategory.OST_Areas]) is in pyrevit or any other library

from pyrevit import revit, DB
from Autodesk.Revit.DB import Transaction

def get_rooms():
    return DB.FilteredElementCollector(revit.doc).OfCategory(DB.BuiltInCategory.OST_Rooms).ToElements()

def get_areas():
    return DB.FilteredElementCollector(revit.doc).OfCategory(DB.BuiltInCategory.OST_Areas).ToElements()


def set_room_parameter(room, area_area_value, area_unit_value):
    # Set Room Area_area & Area_unit in a single transaction
    with revit.Transaction("Set Room Area_area & Area_unit") as tr:
        try:
            value1 = area_area_value
            value2 = area_unit_value
            value_as_string = str(value2)
            value_as_float = float(value1)
            
            room.LookupParameter("Area_area").Set(value_as_float)

            # Check if area_unit_value is not None before setting it
            if value2 is not None:
                room.LookupParameter("Area_unit").Set(value_as_string)

        except Exception as e:
            # Exception will automatically rollback the transaction
            raise e


def get_area_plan_views():
    # Modify the area plan type filter based on your type
    area_plan_type = "AreaPlan"
    
    return [view for view in DB.FilteredElementCollector(revit.doc).OfClass(DB.ViewPlan).ToElements()
            if area_plan_type in view.ViewType.ToString()]

def process_area_plans():
    area_plan_views = get_area_plan_views()
    
    for area_plan_view in area_plan_views:
        # Execute the main logic for each Area Plan
        
        process_all_elements(area_plan_view)
        

def process_all_elements(area_plan_view):
    rooms = get_rooms()
    areas = get_areas()
    
    for area in areas:
        area_parameter = area.Area
        unit_parameter = area.LookupParameter("Unit Type")

        if area_parameter is not None and unit_parameter is not None:
            area_area_value = area_parameter
            area_unit_value = unit_parameter.AsString()  # Use AsString() to get the parameter value as a string

            for room in rooms:
                room_location = room.Location.Point

                area_bounding_box = area.get_BoundingBox(area_plan_view)

                if area_bounding_box is not None:
                    # Check if the room location is inside the bounding box of the associated area plan
                    if (area_bounding_box.Min.X <= room_location.X <= area_bounding_box.Max.X and
                            area_bounding_box.Min.Y <= room_location.Y <= area_bounding_box.Max.Y):
                        # Set the department parameter of the room with the color information
                        set_room_parameter(room, area_area_value, area_unit_value)

if __name__ == "__main__":
    process_area_plans()

Do you know that this forum has a search functionality :wink: ? You could have searched for the function name there to quickly discover that it is in the pyrevit.revit.query module.
Also, the pinned quick start post has a lot of links/references to documentation and tutorials tha should help you discover and unlock pyrevit capabilities.

2 Likes

hi I tried the above function it is working for rectangular rooms and all but if I try non rectangular rooms it functions weirdly(getting data from other Area ) so I updated the function to get boundaries and point .Still not showing correctly can someone shed light on what i am doing wrong here

# coding: utf8

# Metadata
__title__ = "AreatoRoom"
__author__ = "Saurabh S Jha"
__version__ = 'Version = 1.2'
__doc__ = """
Description:
It will put area calculation in Room for room to Tag based on corresponding area

How-to:
-> Run the script

Last update:
- [02.01.2024] - 1.0 RELEASE
"""

import clr
from pyrevit import revit, DB

# Revit API references
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
from Autodesk.Revit.DB import XYZ

class PointInPoly:
    """Determines if a point is inside a given polygon"""
    def is_point_in_polygon(self, polygon, test_point):
        result = False
        j = len(polygon) - 1

        for i in range(len(polygon)):
            if ((polygon[i].Y < test_point.Y and polygon[j].Y >= test_point.Y) or
               (polygon[j].Y < test_point.Y and polygon[i].Y >= test_point.Y)):
                if (polygon[i].X + (test_point.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) *
                   (polygon[j].X - polygon[i].X)) < test_point.X:
                    result = not result
            j = i

        return result

def get_rooms():
    """Fetches all room elements"""
    return DB.FilteredElementCollector(revit.doc).OfCategory(DB.BuiltInCategory.OST_Rooms).ToElements()

def get_areas():
    """Fetches all area elements"""
    return DB.FilteredElementCollector(revit.doc).OfCategory(DB.BuiltInCategory.OST_Areas).ToElements()

def set_room_parameters(room, area):
    """Sets parameters for a single room based on a corresponding area"""
    with revit.Transaction("Update Room Parameters"):
        area_parameters = (area.Area, area.LookupParameter("Unit Type").AsString(),
                           area.LookupParameter("Area Type").AsString(), area.LookupParameter("Color").AsString())
        room.LookupParameter("Area_area").Set(float(area_parameters[0]))
        room.LookupParameter("Area_unit").Set(str(area_parameters[1]))
        room.LookupParameter("Department").Set(str(area_parameters[2]))
        room.LookupParameter("Area_color").Set(str(area_parameters[3]))

def room_contains_point(room, point):
    """Checks if a room contains a given point"""
    boundary_points = get_room_boundary_points(room)
    point_in_poly = PointInPoly()
    return point_in_poly.is_point_in_polygon(boundary_points, point)

def get_room_boundary_points(room):
    """Returns a list of XYZ points representing the room's boundary"""
    boundary_options = DB.SpatialElementBoundaryOptions()
    boundary_options.SpatialElementBoundaryLocation = DB.SpatialElementBoundaryLocation.Center
    boundaries = room.GetBoundarySegments(boundary_options)
    
    points = []
    if boundaries:  # Check if boundaries list is not empty
        for boundary_list in boundaries:
            for segment in boundary_list:
                curve = segment.GetCurve()
                start_point, end_point = curve.GetEndPoint(0), curve.GetEndPoint(1)
                add_point_if_unique(points, start_point)
                add_point_if_unique(points, end_point)
        if points:  # Check if points list is not empty
            points.append(points[0])  # Close the loop
    return points

def add_point_if_unique(points_list, point):
    """Adds a point to the list if it's not already present"""
    if not any(p.IsAlmostEqualTo(point) for p in points_list):
        points_list.append(point)

def process_all_elements():
    """Main function to process rooms and areas based on corresponding area"""
    rooms = get_rooms()
    areas = get_areas()

    for room in rooms:
        if room.Location:
            room_point = room.Location.Point
            for area in areas:
                area_boundary_points = get_room_boundary_points(area)
                if area_boundary_points and room_contains_point(area, room_point):  # Check if area_boundary_points is not empty before proceeding
                    set_room_parameters(room, area)
                    break  # Move to the next room after finding its corresponding area

if __name__ == "__main__":
    process_all_elements()


Haven’t looked at the whole thread but have you tried

IsPointInRoom()

@Jean-Marc suggestion should be the way to go.

Here’s my take at your code to clean it up, with comments of what I did:

# coding: utf8

# Metadata
__title__ = "AreatoRoom"
__author__ = "Saurabh S Jha"
__version__ = 'Version = 1.2'
__doc__ = """
Description:
It will put area calculation in Room for room to Tag based on corresponding area

How-to:
-> Run the script

Last update:
- [02.01.2024] - 1.0 RELEASE
"""
from pyrevit import revit, DB
# get_rooms and get_areas can be shortened as I mentioned before with this function
from pyrevit.revit.query import get_elements_by_categories
# no need to import clr, and add references
# XYZ is not used anywhere directly, noo need to import it

# The class PointInPoly only contains a method and doesn't hold any state,
# so it is better to turn it into a function
def is_point_in_polygon(polygon, test_point):
    """Determines if a point is inside a given polygon"""
    result = False
    # a more pythonic way to get 2 adjacent items in a list is to use zip
    for prev, next in zip(polygon[:-1], polygon[1:]):
        # here I'm using min/max for readability, it could be less performant
        if (min(prev.Y, next.Y) < test_point.Y <= max(prev.Y, next.Y)):
            if (prev.X + (test_point.Y - prev.Y) / (next.Y - prev.Y) *
                (next.X - prev.X)) < test_point.X:
                result = not result
    return result

def set_room_parameters(room, area):
    """Sets parameters for a single room based on a corresponding area"""
    with revit.Transaction("Update Room Parameters"):
        # lists are not memory efficient, just pass the value to Parameter.Set
        room.LookupParameter("Area_area").Set(area.Area)
        room.LookupParameter("Area_unit").Set(area.LookupParameter("Unit Type").AsString())
        room.LookupParameter("Department").Set(area.LookupParameter("Area Type").AsString())
        room.LookupParameter("Area_color").Set(area.LookupParameter("Color").AsString())

def room_contains_point(room, point):
    """Checks if a room contains a given point"""
    boundary_points = get_room_boundary_points(room)
    # moving the emptiness check here, see below
    if not boundary_points:
        return False
    # here we call the extracted function directly
    return is_point_in_polygon(boundary_points, point)

def get_room_boundary_points(room):
    """Returns a list of XYZ points representing the room's boundary"""
    boundary_options = DB.SpatialElementBoundaryOptions()
    boundary_options.SpatialElementBoundaryLocation = DB.SpatialElementBoundaryLocation.Center
    boundaries = room.GetBoundarySegments(boundary_options)
    
    points = []
    # non need to check if the list is empty, the loop will exit anyway
    for boundary_list in boundaries:
        for segment in boundary_list:
            curve = segment.GetCurve()
            # just a cosmetic change here, I hate unneeded tuples packing and unpacking ;)
            add_point_if_unique(points, curve.GetEndPoint(0))
            add_point_if_unique(points, curve.GetEndPoint(1))
    if points:  # Check if points list is not empty
        points.append(points[0])  # Close the loop
    return points

def add_point_if_unique(points_list, point):
    """Adds a point to the list if it's not already present"""
    if not any(p.IsAlmostEqualTo(point) for p in points_list):
        points_list.append(point)

def process_all_elements():
    """Main function to process rooms and areas based on corresponding area"""
    rooms = get_elements_by_categories([DB.BuiltInCategory.OST_Rooms])
    areas = get_elements_by_categories([DB.BuiltInCategory.OST_Areas])

    for room in rooms:
        # here we exit early to avoid deep indentations
        if not room.Location:
            continue
        room_point = room.Location.Point
        for area in areas:
            # you already extract the points in the room_contains_point function,
            # there's no need to do it here
            if room_contains_point(area, room_point):
                set_room_parameters(room, area)
                break  # Move to the next room after finding its corresponding area

if __name__ == "__main__":
    process_all_elements()

Note that this is not tested and can have bugs!

1 Like