Beams length from outer faces

Hi all,

In my Revit model, I have an orthogonal beam network with different symbols, as shown in the image below.

To create longitudinal rebars on the top and bottom faces of each beam, I need to retrieve their total length, measured along their local longitudinal axis from outer face to outer face, as illustrated in the image.

However, since the beams use different symbol, where their b (width) and h (height) parameters are flipped along either the X or Y axis, I haven’t found a reliable way to get the total length.

I tried using Location.Curve, but it only returns the length between the beam’s endpoints, which doesn’t account for the full length from outer face to outer face?

import sys
import clr
import math
from System.Collections.Generic import IList, List

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

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *

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

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

# Collect structural framing elements (beams)
beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .ToElements()

# Build dictionary with beam names and lengths


OUT = beams_length = [
    (b.Name, b.Location.Curve.ToProtoType().Length)
    for b in beams
]```

Hi, you won’t be able to get what you’re looking for from the solid (then the faces and separating them based on their vector components).

Sincerely,
Christian.stan

1 Like

Hi @christian.stan

Have you an example hwo to do that?

Thanks.

1 Like

Hi, there’s no shortage of examples on the forum for obtaining solids and faces from elements.

(I’ll let you search a bit…)
edit:
The vector components will be Z = 1 for the upper face and Z = -1 for the lower face.

Outgoing normal to the material (that’s cool, it’s immutable).

Sincerely,
christian.stan

1 Like

setting up perimeters by picking the outer faces (4 in this case), projecting each mid point onto the faces which their normal direction is either the same or opposite of the direction of beam’s location curve, then DistanceTo(proj 1, proj 2) to get the total length. each length would be paired nicely with its corresponding beam.

1 Like

@christian.stan

I tried this code where I got faces and their Normal vectors for each solid geometry representing a beam as you can see in the output below, what’s next and how can I filter them to get only the outer faces as suggested by @BimAmbit here:

import sys
import clr
import math
from System.Collections.Generic import IList, List

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

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *

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

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

# Collect structural framing elements (beams)
beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .ToElements()


def get_side_face(beam):
    options = Options()
    options.IncludeNonVisibleObjects = False
    options.ComputeReferences = True
    options.DetailLevel = ViewDetailLevel.Fine

    geoElement = beam.get_Geometry(options)
    solid = next((g for g in geoElement if isinstance(g, Solid) and g.Volume > 0), None)
    if not solid:
        return None
    
    if solid:
        Solid_faces = [f for f in solid.Faces]
        faces_normal = [f.FaceNormal for f in Solid_faces]
        return Solid_faces,faces_normal
        
OUT = [get_side_face(beam) for beam in beams]

Thanks.

here is a possibility

edit:
I was using vectors, but if your beam is oriented at 45°, you need to test the x and y components with their sign compared to the direction of the curve, whether oriented or flipped (a bit tedious).

With surfaces (less problems after trimming up and down), except in special cases, it’s faster.

python
Solid_faces = [f for f in solid.Faces if f.FaceNormal == 1 or f.FaceNormal== -1]
Face up an down

other_faces= [f for f in solid.Faces if f.FaceNormal == 0]

cordially
christian.stan

1 Like

@christian.stan
I tried this updated code, where I was able to get the side faces of beams along their locations by filtering them using FaceNormal and Area properties, as shown in the output below. However, I’m only getting the visible face in the current view!..hidden faces are not being taken into account.?.
In the image below, you can see that I’m getting only one face matching the criteria I’m targeting.

import sys
import clr
import math
from System.Collections.Generic import IList, List

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

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *

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

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

# Collect structural framing elements (beams)
beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .ToElements()


def get_side_face(beam):
    options = Options()
    options.IncludeNonVisibleObjects = False
    options.ComputeReferences = True
    options.DetailLevel = ViewDetailLevel.Fine
	
    geoElement = beam.get_Geometry(options)
    solid = next((g for g in geoElement if isinstance(g, Solid) and g.Volume > 0), None)
    if not solid:
        return None
    
    if solid:
        Solid_faces = []
        for face in solid.Faces:
            local_x = beam.Location.Curve.Direction
            product_vect = face.FaceNormal.CrossProduct(local_x)
            if product_vect.IsAlmostEqualTo(XYZ(0,0,1)) or product_vect.IsAlmostEqualTo(XYZ(0,0,-1)):
                Solid_faces.append(face)        
                faces_normal = [f.FaceNormal for f in Solid_faces]
                faces_area = [f.Area*(0.3048**2) for f in Solid_faces]
    return Solid_faces,faces_normal, faces_area
        
OUT = [get_side_face(beam) for beam in beams]

Thanks.

1 Like

@c.poupin

Have you an idea how to solve this ?
Thanks

try this example, the workaround is reverse temporary all jonctions to get full beam solid uncuted

import clr
import sys
import System
from System.Collections.Generic import List, IList, Dictionary
#
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)

#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

import functools

def rollbackTransaction(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        TransactionManager.Instance.ForceCloseTransaction()
        t = Transaction(doc, func.__name__)
        t.Start()
        ret = func(*args, **kwargs)
        t.RollBack()
        t.Dispose()
        return ret      
    return wrapper  

def get_solid(beam):
    options = Options()
    options.IncludeNonVisibleObjects = False
    options.DetailLevel = ViewDetailLevel.Fine
    geoElement = beam.get_Geometry(options)
    solid = next((g for g in geoElement if isinstance(g, DB.Solid) and g.Volume > 0), None)
    return solid

@rollbackTransaction   
def get_full_length_beam(elem):
    cut_elems = [doc.GetElement(xId) for xId in DB.SolidSolidCutUtils.GetCuttingSolids(elem)]
    # reverse all jonctions to get full beam solid uncuted
    for e in cut_elems:
        DB.JoinGeometryUtils.SwitchJoinOrder(doc, elem, e)
    doc.Regenerate()
    max_edge = max(get_solid(elem).Edges, key = lambda x : x.ApproximateLength )
    return max_edge.AsCurve().ToProtoType().Length
#
beam = UnwrapElement(IN[0])

OUT = get_full_length_beam(beam)
2 Likes

@c.poupin

I tested your code, and it works for the beams located at the ends, where I was able to obtain the desired edge with the max length except for the intersecting intermediate beams, as you can see in the image below.

Is there a way to get the original edges (where beams are intersected) from the solid uncuted or using another method?

Here is a variant using BoundingBox.ByMinimumVolume (Dynamo 2.16+)

code Python (PythonNet3), need to adapt for IronPython

import clr
import sys
import System
from System.Collections.Generic import List, IList, Dictionary
#
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)

#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

import functools

def rollbackTransaction(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        TransactionManager.Instance.ForceCloseTransaction()
        t = Transaction(doc, func.__name__)
        t.Start()
        ret = func(*args, **kwargs)
        t.RollBack()
        t.Dispose()
        return ret      
    return wrapper  

def get_solid(beam):
    options = Options()
    options.IncludeNonVisibleObjects = False
    options.DetailLevel = ViewDetailLevel.Fine
    geoElement = beam.get_Geometry(options)
    solid = next((g for g in geoElement if isinstance(g, DB.Solid) and g.Volume > 0), None)
    return solid

@rollbackTransaction   
def get_full_length_beam(elem):
    curve_beam = elem.Location.Curve
    cut_elems = [doc.GetElement(xId) for xId in DB.SolidSolidCutUtils.GetCuttingSolids(elem)]
    # reverse all jonctions to get full beam solid uncuted
    for e in cut_elems:
        DB.JoinGeometryUtils.SwitchJoinOrder(doc, elem, e)
    doc.Regenerate()
    # convert to prototype and get aligned solid
    ds_solid = get_solid(elem).ToProtoType()
    ds_align_bbx = DS.BoundingBox.ByMinimumVolume([ds_solid])
    ds_align_solid = DS.BoundingBox.ToCuboid(ds_align_bbx)
    # get max edge
    max_ds_edge = max(ds_align_solid.Edges, key = lambda x : x.CurveGeometry.Length )
    return round(max_ds_edge.CurveGeometry.Length , 2)
#
beam = UnwrapElement(IN[0])

OUT = get_full_length_beam(beam)