Changing python code to use surfaces instead of rooms

Hi,

I have some code that I’d like to modify to use surfaces instead of rooms. Could someone help me with this? I’m not very familiar with programming in Dynamo/Python.

import clr
import sys
import os

localapp = os.getenv(r'LOCALAPPDATA')

sys.path.append(os.path.join(localapp, r'python-3.8.10-embed-amd64\Lib\site-packages'))

import System
#
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS

# Import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

import numpy as np
       
def cosine_similarity(point, vector):
    dot_prod = point.DotProduct(vector)
    mag_point = point.GetLength()
    mag_vector = vector.GetLength()
    # avoid division by zero if the magnitude is zero
    if mag_point == 0 or mag_vector == 0:
        return 0
    return dot_prod / (mag_point * mag_vector)

# Preparing input from Dynamo to Revit (a list of rooms)
rooms = [UnwrapElement(room) for room in IN[0]]  # Unwrap the list of rooms
out = []
lst_debug = []
DEBUG = False

# Iterate over each room
for room in rooms:
    bound_opt = SpatialElementBoundaryOptions()
    bound_opt.SpatialElementBoundaryLocation = SpatialElementBoundaryLocation.Finish
    room_curves = [s.GetCurve() for lst_segments in room.GetBoundarySegments(bound_opt) for s in lst_segments]
    ds_room_curves = [c.ToProtoType() for c in room_curves]

    # Coordinates system (vectors) orientation of the room
    lst_vector_ray = [XYZ.BasisX, XYZ.BasisY]
    # Get min point start
    ptstart = room.get_BoundingBox(None).Min
    ptstart = XYZ(ptstart.X, ptstart.Y, room_curves[0].GetEndPoint(0).Z)
    ptend = room.get_BoundingBox(None).Max
    ptend = XYZ(ptend.X, ptend.Y, room_curves[0].GetEndPoint(0).Z)
    max_size = max([ptend.Y - ptstart.Y, ptend.X - ptstart.X])

    # Scan with vector_ray_A and then vector_ray_B
    for idx, vector_ray in enumerate(lst_vector_ray):
        rayA = DB.Line.CreateUnbound(ptstart, vector_ray)
        # Offset ray each scan iteration
        for step in np.arange(0, max_size, 0.0033):
            perpendicular_vector = lst_vector_ray[abs(idx-1)]  # Auto-switch between the inverse of idx
            ray_curve = rayA.CreateOffset(step, perpendicular_vector.CrossProduct(vector_ray))
            # DEBUG to ensure the ray location is correct drawing a small line of ray
            if DEBUG:
                line_bound = ray_curve.Clone()
                line_bound.MakeBound(0.1, 3.9)
                ds_line_bound = line_bound.ToProtoType()
                lst_debug.append(ds_line_bound)
            temp = []
            # Try to find room boundaries intersection
            for bound_curve in room_curves:
                outResultArray = IntersectionResultArray()
                comparisonResult, outResultArray = ray_curve.Intersect(bound_curve, outResultArray)
                if comparisonResult == SetComparisonResult.Overlap:
                    lst_intersect_pts = [i.XYZPoint for i in outResultArray]
                    temp.extend(lst_intersect_pts)
            # Sort points by vector
            sorted_points = sorted(temp, key=lambda p: cosine_similarity(p, vector_ray))
            # Iterate over pair points
            sorted_points = iter(sorted_points)
            for pta, ptb in zip(sorted_points, sorted_points):
                if 0.1 < pta.DistanceTo(ptb) <= 1.8 * 3.281:  # If distance between points is under 1.8m
                    line_bad_part_area = DB.Line.CreateBound(pta, ptb)
                    out.append(line_bad_part_area.ToProtoType())  # Append the result for each room

# Output all the lines found in all rooms
OUT = out

Hi,

you need to replace
get_BoundingBox(None)
by
get_BoundingBox(view_area_plan)

where the view_area_plan is a view where the Area is visible

It’s not working. I provided my floor plan view, but I got an error saying that surfaces don’t have the attribute ‘GetBoundarySegments.’ I removed that part since I already have the surface and don’t need to get it from a room. However, it’s still not working.

Best to share a graph and Revit model that you’re working with - if someone (say myself or @c.poupin) were to try and code something up in say Revit 2025 using a quickly generated sample model, it might not work for you as you’re in Revit 2024, or maybe your surface has a shape edit, or maybe…

1 Like

I’m currently working in Revit 2023 with a sample model. I have just one floor with variously shaped rooms.
1,8m.dyn (230.9 KB)
1,8m check.rvt (5.3 MB)

1 Like

Now in transit for a team meeting which means I likely won’t be able to look at this for a week, but now others can help directly. I’ll take a look if I can. :slight_smile:

try this process

  1. Instead of using room.GetBoundarySegments(), retrieve surface curves with the surface.PerimeterCurves() method
  2. convert curves to Revit type with the ToRevitType() extension method
  3. obtain the surface’s BoudingBox with surface.BoundingBox property
  4. get the min and max points of BoundingBox and convert them with the ToXyz() extension method

At this point, you should have obtained the following variables:

  • room_curves
  • ds_room_curves
  • ptstart
  • ptend

the rest of the code should remain unchanged

Resources

1 Like