Transform curves extracted from beams

Hi All, @c.poupin

In the main purpose to create beams rebars and in continuation of my previous topic Beams length from outer faces, where I was able to retrieve and extend the max_edge curves from an intersecting beam network using uncut solid geometry, my main goal now is to improve the code and make it cleaner starting by properly setting and getting beam covers, and then applying the necessary transformations to the extracted curves accordingly.
When I run the code, the cover parameters are updated as expected from the input values and correctly displayed in the properties tab for all beams., however unlike top_cover and btm_cover I’m having an issue retrieving the side_cover parameter within curve_multiply_offset function as you can see in the error displayed in the image below and this prevents the required curve transformations from being applied within the main function get_corrected_beam_length.

Please check my attached code and revit file:

Transform curves
import clr
import sys
import System
from System.Collections.Generic import IList, List

clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

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

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

clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument


clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)


rebar_types = (FilteredElementCollector(doc)
        .OfCategory(BuiltInCategory.OST_Rebar)
        .WhereElementIsElementType()
        .WherePasses(ElementClassFilter(RebarBarType))
        .ToElements())
        
top_bar_type = next((r for r in rebar_types if r.LookupParameter('Diamètre de barre').AsValueString() == "12 mm"), None)
btm_bar_type = next((r for r in rebar_types if r.LookupParameter('Diamètre de barre').AsValueString() == "14 mm"), None)


stirrup_type = next((r for r in rebar_types if r.LookupParameter('Diamètre de barre').AsValueString() == "6 mm"), None)

hook_types = list(FilteredElementCollector(doc)
                  .OfClass(RebarHookType)
                  .ToElements())

stirrup_hook_types = [hk for hk in hook_types if hk.Style == RebarStyle.StirrupTie]
standard_hook_types = [hk for hk in hook_types if hk.Style == RebarStyle.Standard]        

hook_90 = next((h for h in standard_hook_types if Element.Name.GetValue(h) == "Standard - 90 deg."), None)

hook_135 = next((h for h in stirrup_hook_types if Element.Name.GetValue(h) == "Etrier/épingle - 135 deg."), None)

cover_faces = {
    "Top": BuiltInParameter.CLEAR_COVER_TOP,
    "Bottom": BuiltInParameter.CLEAR_COVER_BOTTOM,
    "Other": BuiltInParameter.CLEAR_COVER_OTHER
}

cover_types = list(FilteredElementCollector(doc).OfClass(RebarCoverType).ToElements())

def get_cover_type(distance):
    for ct in cover_types:
        if abs(ct.CoverDistance - distance) < 1e-6:
            return ct
    name = "Enrobage_{} cm".format(distance / 0.0328084)
    new_ct = RebarCoverType.Create(doc, name, distance)
    cover_types.append(new_ct)
    return new_ct

def set_beam_covers(beam, top, btm, side):
    cover_cm = {"Top": top, "Bottom": btm, "Other": side}
    cover_ft = {face: cover_cm[face] * 0.0328084 for face in cover_cm}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = get_cover_type(cover_ft[face])
            if ct:
                param.Set(ct.Id)

def get_beam_covers(beam):
    result = {}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = doc.GetElement(param.AsElementId())
            if isinstance(ct, RebarCoverType):
                result[face] = ct.CoverDistance
    return result

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

def get_side_face_normal(solid, edge):
    for face in solid.Faces:
        if edge in [e for loop in face.EdgeLoops for e in loop]:
            normal = face.FaceNormal
            if not (normal.IsAlmostEqualTo(XYZ.BasisZ) or normal.IsAlmostEqualTo(-XYZ.BasisZ)):
                return normal
    return None

def is_curves_parallel(curve1, curve2, tolerance=1e-6):
    vect1 = curve1.Direction.Normalize()
    vect2 = curve2.Direction.Normalize()
    
    return vect1.CrossProduct(vect2).GetLength() <= tolerance
    
def curve_multiply_offset(curve, vector, distance, top_cover, btm_cover, side_cover):
    
    curve = curve.CreateTransformed(Transform.CreateTranslation(XYZ(0, 0, -1).Multiply(top_cover)))
    vertical_offset = distance - (top_cover + btm_cover)

    trans = Transform.CreateTranslation(vector.Negate().Multiply(side_cover))
    top_curve = curve.CreateTransformed(trans)
    bottom_trans = Transform.CreateTranslation(XYZ(0, 0, -1).Multiply(vertical_offset))        
    btm_curve = top_curve.CreateTransformed(bottom_trans)
    btm_curve = Line.CreateBound(btm_curve.GetEndPoint(1), btm_curve.GetEndPoint(0))
    return top_curve, btm_curve 

def get_intersecting_end_beams_data(single_beam, all_beams, tolerance=0.001):
    loc_curve = single_beam.Location.Curve
    w = single_beam.Symbol.LookupParameter("b").AsDouble()
    h = single_beam.Symbol.LookupParameter("h").AsDouble()
    single_beam_covers = get_beam_covers(single_beam)
    top_cover = single_beam_covers.get("Top")
    btm_cover = single_beam_covers.get("Bottom")
    side_cover = single_beam_covers.get("Other")
    
    start = loc_curve.GetEndPoint(0)
    end = loc_curve.GetEndPoint(1)
    w_start = None
    w_end = None
    start_side_cover = 0
    end_side_cover = 0
    
    for beam in all_beams:
        if beam.Id == single_beam.Id:
            continue
        other_curve = beam.Location.Curve
        b = beam.Symbol.LookupParameter("b").AsDouble()
        other_beam_covers = get_beam_covers(beam)
        other_side_cover = other_beam_covers.get("Other")
        if is_curves_parallel(loc_curve, other_curve):
            continue
        
        projected_start = other_curve.Project(start)
        if projected_start and projected_start.Distance < tolerance and w_start is None:
            w_start = b / 2
            start_side_cover = other_side_cover
            print(start_side_cover * 0.3048)
        projected_end = other_curve.Project(end)
        if projected_end and projected_end.Distance < tolerance and w_end is None:
            w_end = b / 2
            end_side_cover = other_side_cover
            print(end_side_cover * 0.3048)
    return {
        "beam_width": w,
        "beam_height": h,
        "beam_top_cover": top_cover,
        "beam_btm_cover": btm_cover,
        "beam_side_cover": side_cover,
        "width_start": w_start,
        "width_end": w_end,
        "beam_start_side_cover": start_side_cover,
        "beam_end_side_cover": end_side_cover
    }




def get_corrected_beam_length(beam, all_beams):
    beam_curve = beam.Location.Curve
    direction = beam_curve.Direction
    beam_length = beam_curve.Length
    start = beam_curve.GetEndPoint(0)

    cut_elems = [doc.GetElement(xId) for xId in SolidSolidCutUtils.GetCuttingSolids(beam)]
    for e in cut_elems:
        JoinGeometryUtils.SwitchJoinOrder(doc, beam, e)
    doc.Regenerate()

    solid = get_solid(beam)
    if not solid:
        return None

    max_edge = max(solid.Edges, key=lambda x: x.ApproximateLength)
    max_edge_curve = max_edge.AsCurve()
    max_edge_direction = max_edge_curve.Direction
    max_edge_length = max_edge_curve.Length
    face_normal = get_side_face_normal(solid, max_edge)
    if face_normal is None:
        return None
    
    intersection_data = get_intersecting_end_beams_data(beam, all_beams)
    beam_width = intersection_data["beam_width"]
    beam_height = intersection_data["beam_height"]
    top_cover = intersection_data["beam_top_cover"]
    btm_cover = intersection_data["beam_btm_cover"]
    side_cover = intersection_data["beam_side_cover"]
    w_start = intersection_data["width_start"] or 0
    w_end = intersection_data["width_end"] or 0
    start_side_cover = intersection_data["beam_start_side_cover"] 
    end_side_cover = intersection_data["beam_end_side_cover"]
    
    if max_edge_length > beam_length:
        if not max_edge_curve.GetEndPoint(0).Z == beam_curve.GetEndPoint(0).Z:
            max_edge_curve = Line.CreateBound(XYZ(max_edge_curve.GetEndPoint(0).X, max_edge_curve.GetEndPoint(0).Y, beam_curve.GetEndPoint(0).Z), XYZ(max_edge_curve.GetEndPoint(1).X, max_edge_curve.GetEndPoint(1).Y, beam_curve.GetEndPoint(1).Z))
        if not max_edge_direction.IsAlmostEqualTo(direction):
            max_edge_curve = Line.CreateBound(max_edge_curve.GetEndPoint(1), max_edge_curve.GetEndPoint(0))
        new_start = max_edge_curve.Evaluate(start_side_cover / max_edge_length, True)
        new_end = max_edge_curve.Evaluate(1 - (end_side_cover / max_edge_length), True)
        new_line = Line.CreateBound(new_start, new_end)
    else:
        total_length = beam_length + w_start + w_end - (start_side_cover + end_side_cover)
        project_point = start + face_normal.Multiply(beam_width / 2)
        new_start = project_point - direction.Multiply(w_start - start_side_cover)
        new_end = new_start + direction.Multiply(total_length)
        new_line = Line.CreateBound(new_start, new_end)

    top_curve, btm_curve = curve_multiply_offset(new_line, face_normal, beam_height, top_cover, btm_cover, side_cover)
      
    return btm_curve, top_curve, face_normal
    
    
def split_curve(curve, bartype):
    total_length = curve.Length
    unit_length = 12.0 / 0.3048
    diameter = bartype.BarModelDiameter
    lr = 50 * diameter
    min_remainder = 2.0 / 0.3048
    tol_near_full = 0.2 / 0.3048
    direction = curve.Direction

    
    curves = []
    start = curve.GetEndPoint(0)
    k = 0
    n = int(total_length // unit_length)
    
    if total_length <= unit_length:
        curves.append(curve)
    else:    
        while k < n:
            end = start.Add(direction.Multiply(unit_length))
            new_line = Line.CreateBound(start, end)
            curves.append(new_line)
            k += 1

            
            next_start = start.Add(direction.Multiply(unit_length - lr))

            
            remaining_length = curve.GetEndPoint(1).DistanceTo(end)

            
            if remaining_length < unit_length - lr:
        
                remainder_end = curve.GetEndPoint(1)
        
                if remaining_length <= lr or remaining_length < min_remainder:
            
                    adjusted_start = remainder_end.Add(direction.Multiply(min_remainder).Negate())
                    remainder_line = Line.CreateBound(adjusted_start, remainder_end)
            
                else:
                    remainder_line = Line.CreateBound(next_start, remainder_end)
                    if remainder_line.Length < (unit_length - tol_near_full):
                        remainder_line = remainder_line
                    else:
                        adjusted_start = remainder_end.Add(direction.Multiply(unit_length).Negate())
                        remainder_line = Line.CreateBound(adjusted_start, remainder_end)
            
                curves.append(remainder_line)
                break

            
            start = next_start
    return curves
    


beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .Where(System.Func[DB.Element, System.Boolean](lambda b : b.StructuralUsage == StructuralInstanceUsage.Girder))\
    .ToList()
    
with Transaction(doc, "Set beams Covers") as t:
    t.Start()
    for b in beams:
        set_beam_covers(b, 10, 15, 8)
        
    t.Commit()

btm_curves = [] 
top_curves = []

with Transaction(doc, "split rebars") as t:
    t.Start()
    for beam in beams:
        direction = beam.Location.Curve.Direction
        width = beam.Symbol.LookupParameter("b").AsDouble()
        covers = get_beam_covers(beam)
        result = get_corrected_beam_length(beam, beams)  
        if not result:
            continue
        btm_curve, top_curve, normal_vect = result
        bc = split_curve(btm_curve, btm_bar_type)
        tc = split_curve(top_curve, top_bar_type)  
        btm_curves.append(bc)
        top_curves.append(tc)
    t.Commit()     


OUT = [c.ToProtoType() for lst in btm_curves for c in lst], [c.ToProtoType() for lst in top_curves for c in lst]

beams_network1.rvt (1.9 MB)

Any help to solve this issue would be greatly appreciated.

Thanks.

I don’t have the same error. Rather than providing the complete code, try providing the part of the code that is causing the problem.

2 Likes

@c.poupin
here the edited code without split_curve function

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

clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

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

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

clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument


clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)


# cover parameters by faces
cover_faces = {
    "Top": BuiltInParameter.CLEAR_COVER_TOP,
    "Bottom": BuiltInParameter.CLEAR_COVER_BOTTOM,
    "Other": BuiltInParameter.CLEAR_COVER_OTHER
}

cover_types = list(FilteredElementCollector(doc).OfClass(RebarCoverType).ToElements())

def get_cover_type(distance):
    for ct in cover_types:
        if abs(ct.CoverDistance - distance) < 1e-6:
            return ct
    name = "Enrobage_{} cm".format(distance / 0.0328084)
    new_ct = RebarCoverType.Create(doc, name, distance)
    cover_types.append(new_ct)
    return new_ct

def set_beam_covers(beam, top, btm, side):
    cover_cm = {"Top": top, "Bottom": btm, "Other": side}
    cover_ft = {face: cover_cm[face] * 0.0328084 for face in cover_cm}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = get_cover_type(cover_ft[face])
            if ct:
                param.Set(ct.Id)

def get_beam_covers(beam):
    result = {}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = doc.GetElement(param.AsElementId())
            if isinstance(ct, RebarCoverType):
                result[face] = ct.CoverDistance
    return result

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

def get_side_face_normal(solid, edge):
    for face in solid.Faces:
        if edge in [e for loop in face.EdgeLoops for e in loop]:
            normal = face.FaceNormal
            if not (normal.IsAlmostEqualTo(XYZ.BasisZ) or normal.IsAlmostEqualTo(-XYZ.BasisZ)):
                return normal
    return None

def is_curves_parallel(curve1, curve2, tolerance=1e-6):
    vect1 = curve1.Direction.Normalize()
    vect2 = curve2.Direction.Normalize()
    
    return vect1.CrossProduct(vect2).GetLength() <= tolerance
    
def curve_multiply_offset(curve, vector, distance, top_cover, btm_cover, side_cover):
    
    curve = curve.CreateTransformed(Transform.CreateTranslation(XYZ(0, 0, -1).Multiply(top_cover)))
    vertical_offset = distance - (top_cover + btm_cover)

    trans = Transform.CreateTranslation(vector.Negate().Multiply(side_cover))
    top_curve = curve.CreateTransformed(trans)
    bottom_trans = Transform.CreateTranslation(XYZ(0, 0, -1).Multiply(vertical_offset))        
    btm_curve = top_curve.CreateTransformed(bottom_trans)
    btm_curve = Line.CreateBound(btm_curve.GetEndPoint(1), btm_curve.GetEndPoint(0))
    return top_curve, btm_curve 

def get_intersecting_end_beams_data(single_beam, all_beams, tolerance=0.001):
    loc_curve = single_beam.Location.Curve
    w = single_beam.Symbol.LookupParameter("b").AsDouble()
    h = single_beam.Symbol.LookupParameter("h").AsDouble()
    single_beam_covers = get_beam_covers(single_beam)
    top_cover = single_beam_covers.get("Top")
    btm_cover = single_beam_covers.get("Bottom")
    side_cover = single_beam_covers.get("Other")
    
    start = loc_curve.GetEndPoint(0)
    end = loc_curve.GetEndPoint(1)
    w_start = None
    w_end = None
    start_side_cover = 0
    end_side_cover = 0
    
    for beam in all_beams:
        if beam.Id == single_beam.Id:
            continue
        other_curve = beam.Location.Curve
        b = beam.Symbol.LookupParameter("b").AsDouble()
        other_beam_covers = get_beam_covers(beam)
        other_side_cover = other_beam_covers.get("Other")
        if is_curves_parallel(loc_curve, other_curve):
            continue
        
        projected_start = other_curve.Project(start)
        if projected_start and projected_start.Distance < tolerance and w_start is None:
            w_start = b / 2
            start_side_cover = other_side_cover
            print(start_side_cover * 0.3048)
        projected_end = other_curve.Project(end)
        if projected_end and projected_end.Distance < tolerance and w_end is None:
            w_end = b / 2
            end_side_cover = other_side_cover
            print(end_side_cover * 0.3048)
    return {
        "beam_width": w,
        "beam_height": h,
        "beam_top_cover": top_cover,
        "beam_btm_cover": btm_cover,
        "beam_side_cover": side_cover,
        "width_start": w_start,
        "width_end": w_end,
        "beam_start_side_cover": start_side_cover,
        "beam_end_side_cover": end_side_cover
    }




def get_corrected_beam_length(beam, all_beams):
    beam_curve = beam.Location.Curve
    direction = beam_curve.Direction
    beam_length = beam_curve.Length
    start = beam_curve.GetEndPoint(0)

    cut_elems = [doc.GetElement(xId) for xId in SolidSolidCutUtils.GetCuttingSolids(beam)]
    for e in cut_elems:
        JoinGeometryUtils.SwitchJoinOrder(doc, beam, e)
    doc.Regenerate()

    solid = get_solid(beam)
    if not solid:
        return None

    max_edge = max(solid.Edges, key=lambda x: x.ApproximateLength)
    max_edge_curve = max_edge.AsCurve()
    max_edge_direction = max_edge_curve.Direction
    max_edge_length = max_edge_curve.Length
    face_normal = get_side_face_normal(solid, max_edge)
    if face_normal is None:
        return None
    
    intersection_data = get_intersecting_end_beams_data(beam, all_beams)
    beam_width = intersection_data["beam_width"]
    beam_height = intersection_data["beam_height"]
    top_cover = intersection_data["beam_top_cover"]
    btm_cover = intersection_data["beam_btm_cover"]
    side_cover = intersection_data["beam_side_cover"]
    w_start = intersection_data["width_start"] or 0
    w_end = intersection_data["width_end"] or 0
    start_side_cover = intersection_data["beam_start_side_cover"] 
    end_side_cover = intersection_data["beam_end_side_cover"]
    
    if max_edge_length > beam_length:
        if not max_edge_curve.GetEndPoint(0).Z == beam_curve.GetEndPoint(0).Z:
            max_edge_curve = Line.CreateBound(XYZ(max_edge_curve.GetEndPoint(0).X, max_edge_curve.GetEndPoint(0).Y, beam_curve.GetEndPoint(0).Z), XYZ(max_edge_curve.GetEndPoint(1).X, max_edge_curve.GetEndPoint(1).Y, beam_curve.GetEndPoint(1).Z))
        if not max_edge_direction.IsAlmostEqualTo(direction):
            max_edge_curve = Line.CreateBound(max_edge_curve.GetEndPoint(1), max_edge_curve.GetEndPoint(0))
        new_start = max_edge_curve.Evaluate(start_side_cover / max_edge_length, True)
        new_end = max_edge_curve.Evaluate(1 - (end_side_cover / max_edge_length), True)
        new_line = Line.CreateBound(new_start, new_end)
    else:
        total_length = beam_length + w_start + w_end - (start_side_cover + end_side_cover)
        project_point = start + face_normal.Multiply(beam_width / 2)
        new_start = project_point - direction.Multiply(w_start - start_side_cover)
        new_end = new_start + direction.Multiply(total_length)
        new_line = Line.CreateBound(new_start, new_end)

    top_curve, btm_curve = curve_multiply_offset(new_line, face_normal, beam_height, top_cover, btm_cover, side_cover)
      
    return btm_curve, top_curve, face_normal
    
    
# Collect beams
beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .Where(System.Func[DB.Element, System.Boolean](lambda b : b.StructuralUsage == StructuralInstanceUsage.Girder))\
    .ToList()
    
with Transaction(doc, "Set beams Covers") as t:
    t.Start()
    for b in beams:
        set_beam_covers(b, 10, 15, 8)
        
    t.Commit()

btm_curves = [] 
top_curves = []

with Transaction(doc, "split rebars") as t:
    t.Start()
    for beam in beams:
        direction = beam.Location.Curve.Direction
        width = beam.Symbol.LookupParameter("b").AsDouble()
        covers = get_beam_covers(beam)
        result = get_corrected_beam_length(beam, beams)
        if not result:
            continue
        btm_curve, top_curve, normal_vect = result
        
        btm_curves.append(btm_curve)
        top_curves.append(top_curve)
    t.Commit()     


OUT = [c.ToProtoType() for c in btm_curves], [c.ToProtoType() for c in top_curves]

Thanks.

There are six built in parameters that relate to rebar cover - it’s quite possible that the ‘other’ value is None.

CLEAR_COVER       “Rebar Cover”
CLEAR_COVER_BOTTOM   “Rebar Cover - Bottom Face”
CLEAR_COVER_TOP      “Rebar Cover - Top Face”
CLEAR_COVER_OTHER    “Rebar Cover - Other Faces”
CLEAR_COVER_INTERIOR  “Rebar Cover - Interior Face”
CLEAR_COVER_EXTERIOR “Rebar Cover - Exterior Face”

1 Like

Hi @Mike.Buttery

I’m sorry for the delayed feedback …I was traveling and couldn’t respond earlier!

I’m not sure how to access all those parameters. The beams I collected only include the cover parameters I’m currently working with..the ones visible under the Properties tab. I also found them using Revit Lookup, as you can see in the image below:

I also found this topic on the Revit API forum discussing rebar cover parameters. It uses the same built-in parameters I’m currently accessing to update cover values, so I’m not sure when or why the other parameters you mentioned would be used.

Thanks.

Beams do use the three cover parameters in your code, so not adjustment required - therefore some of the parameters appear to not have a value. You could add a LogicalAndFilter to your beam collector to ensure all elements have the parameters you require.
Example code:

import clr

clr.AddReference("RevitServices")
from RevitServices.Persistence import DocumentManager

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


def logical_and_filter(bips):
    pfrs = [
        ParameterFilterRuleFactory.CreateHasValueParameterRule(ElementId(bip))
        for bip in bips
    ]
    return LogicalAndFilter([ElementParameterFilter(pfr) for pfr in pfrs])


doc = DocumentManager.Instance.CurrentDBDocument

laf = logical_and_filter(
    [
        BuiltInParameter.CLEAR_COVER_BOTTOM,
        BuiltInParameter.CLEAR_COVER_TOP,
        BuiltInParameter.CLEAR_COVER_OTHER,
    ]
)

OUT = (
    FilteredElementCollector(doc)
    .WherePasses(laf)
    .OfClass(FamilyInstance)
    .OfCategory(BuiltInCategory.OST_StructuralFraming)
    .ToElements()
)
2 Likes

@Mike.Buttery

I’m not sure where you’re going with checking for the existence of these three parameters, since beams already use only these three cover parameters… this can easily be confirmed by viewing the beam’s cover BuiltInParameters in the Properties tab or by inspecting them with Revit Lookup.

Furthermore, all structural beams in my project have the usage set to StructuralInstanceUsage.Girder. So, if I apply your code, it returns all beams in the project (as shown in the image below), which means the LogicalAndFilter would be redundant in this case.

I believe the issue lies elsewhere… possibly related to an incorrect definition or use of the curve_multiply_offset function within the main get_corrected_beam_length function.
For context, the curve_multiply_offset function is used to transform the extracted curve (the longest curve, which I call max_edge, obtained from the beam’s uncut solid geometry) into the top_curve and btm_curve, positioning them correctly according to the three cover parameters: top_cover, btm_cover, and side_cover.
Thanks.

One of your cover parameters is returning None so you have to exclude it (or deal with it)
image

Edit: Also look at possibility of using RebarHostData Class to access the cover information

1 Like

@c.poupin

Effectively, I found the source of my problem… it occurs after temporarily reversing all beam junctions to obtain the full uncut beam solids, which I use to extract the max_edge in the main function get_corrected_beam_length…the issue originates from these lines inside that function:

def get_corrected_beam_length(beam, intersection_data, all_beams):
    ...
    ...
    cut_elems = [doc.GetElement(xId) for xId in SolidSolidCutUtils.GetCuttingSolids(beam)]
    for e in cut_elems:
        JoinGeometryUtils.SwitchJoinOrder(doc, beam, e)
    doc.Regenerate()
    ...
    ...

In this case, even though the Top and Bottom cover parameters are correctly updated and displayed in the Properties tab for all beams after calling the set_beam_covers function (and before reversing the beam junctions), the Other cover parameter loses its value once Revit regenerates the beam junctions. As a result, the Properties tab shows <multiples> for this parameter instead of the actual assigned value.
I tested the code below, which demonstrates this issue as shown in the following image.

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


clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

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

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

clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

cover_faces = {
    "Top": BuiltInParameter.CLEAR_COVER_TOP,
    "Bottom": BuiltInParameter.CLEAR_COVER_BOTTOM,
    "Other": BuiltInParameter.CLEAR_COVER_OTHER
}

cover_types = list(FilteredElementCollector(doc).OfClass(RebarCoverType).ToElements())


def get_cover_type(distance):
    for ct in cover_types:
        if abs(ct.CoverDistance - distance) < 1e-6:
            return ct
    name = "Enrobage_{:.0f}cm".format(distance / 0.0328084)
    new_ct = RebarCoverType.Create(doc, name, distance)
    cover_types.append(new_ct)
    return new_ct

def set_beam_covers(beam, top, btm, side):
    cover_cm = {"Top": top, "Bottom": btm, "Other": side}
    cover_ft = {face: cover_cm[face] * 0.0328084 for face in cover_cm}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = get_cover_type(cover_ft[face])
            if ct:
                param.Set(ct.Id)
                
def get_beam_covers(beam):
    result = {}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = doc.GetElement(param.AsElementId())
            if isinstance(ct, RebarCoverType):
                result[face] = ct.CoverDistance * 30.48
    return result


def get_solid(elem):
    opt = Options()
    opt.ComputeReferences = True
    opt.IncludeNonVisibleObjects = True
    geoSet = elem.get_Geometry(opt)
    for geo in geoSet:
        if isinstance(geo, Solid) and geo.Volume > 0:
            return geo
    for geo in geoSet:
        if isinstance(geo, GeometryInstance):
            geoSetInst = geo.GetInstanceGeometry()
            for geoI in geoSetInst:
                if isinstance(geoI, Solid) and geoI.Volume > 0:
                    return geoI
    return None

def get_beam_covers_after_cutting(beam):

    
    cut_elems = [doc.GetElement(xId) for xId in SolidSolidCutUtils.GetCuttingSolids(beam)]
    for e in cut_elems:
        JoinGeometryUtils.SwitchJoinOrder(doc, beam, e)
    doc.Regenerate()

    solid = get_solid(beam)
    if not solid:
        return None
    return get_beam_covers(beam)    
        
beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .Where(System.Func[DB.Element, System.Boolean](lambda b: b.StructuralUsage == StructuralInstanceUsage.Girder))\
    .ToList()


with Transaction(doc, "Set beams Covers") as t:
    t.Start()
    for b in beams:
        set_beam_covers(b, 10, 15, 8)
    t.Commit()

results_after_updating_cover = []

with Transaction(doc, "Compute Beam Rebar Curves") as t:
    t.Start()
    for beam in beams:
        results_after_updating_cover.append(get_beam_covers_after_cutting(beam))

    t.Commit()

OUT = results_after_updating_cover

How to adress this issue and properly reassign covers to beams after all junctions are regenerated?

Thanks.

@c.poupin

Please help me to solve this issue

Thanks.

I’ll try to look into it when I have a bit more time.

I’m not a structural engineer so I’m not making any promises.

1 Like

can you try to useJoinGeometryUtils.SwitchJoinOrder before to set_beam_covers ?

1 Like

I tried to set_beam_covers afeter JoinGeometryUtils.SwitchJoinOrder in the code below as you suggested where I got values for all covers keys as you can see in the output below, however some beams still displaying for missing values in the properties tab, and by inspecting that using Revit Lookup, I noticed that their property HasValue is False and their Element Id is non valid and set to -1

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

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

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

clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

cover_faces = {
    "Top": BuiltInParameter.CLEAR_COVER_TOP,
    "Bottom": BuiltInParameter.CLEAR_COVER_BOTTOM,
    "Other": BuiltInParameter.CLEAR_COVER_OTHER
}

cover_types = list(FilteredElementCollector(doc).OfClass(RebarCoverType).ToElements())


def get_cover_type(distance):
    for ct in cover_types:
        if abs(ct.CoverDistance - distance) < 1e-6:
            return ct
    name = "Enrobage_{:.0f}cm".format(distance / 0.0328084)
    new_ct = RebarCoverType.Create(doc, name, distance)
    cover_types.append(new_ct)
    return new_ct

def set_beam_covers(beam, top, btm, side):
    cover_cm = {"Top": top, "Bottom": btm, "Other": side}
    cover_ft = {face: cover_cm[face] * 0.0328084 for face in cover_cm}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = get_cover_type(cover_ft[face])
            if ct:
                param.Set(ct.Id)
                
def get_beam_covers(beam):
    result = {}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = doc.GetElement(param.AsElementId())
            if isinstance(ct, RebarCoverType):
                result[face] = ct.CoverDistance * 30.48
    return result

def get_solid(elem):
    opt = Options()
    opt.ComputeReferences = True
    opt.IncludeNonVisibleObjects = True
    geoSet = elem.get_Geometry(opt)
    for geo in geoSet:
        if isinstance(geo, Solid) and geo.Volume > 0:
            return geo
    for geo in geoSet:
        if isinstance(geo, GeometryInstance):
            geoSetInst = geo.GetInstanceGeometry()
            for geoI in geoSetInst:
                if isinstance(geoI, Solid) and geoI.Volume > 0:
                    return geoI
    return None

def get_beam_covers_after_cutting(beam):

    cut_elems = [doc.GetElement(xId) for xId in SolidSolidCutUtils.GetCuttingSolids(beam)]
    for e in cut_elems:
        JoinGeometryUtils.SwitchJoinOrder(doc, beam, e)
    doc.Regenerate()

    solid = get_solid(beam)
    if not solid:
        return None
    return get_beam_covers(beam)    
        
beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .Where(System.Func[DB.Element, System.Boolean](lambda b: b.StructuralUsage == StructuralInstanceUsage.Girder))\
    .ToList()


results_after_uncut = []
results_for_setting_covers = []

with Transaction(doc, "Compute Beam Rebar Curves") as t:
    t.Start()
    for beam in beams:
        results_after_updating_cover.append(get_beam_covers_after_cutting(beam))
        set_beam_covers(beam, 10, 15, 8)
        results.append(get_beam_covers(beam))

    t.Commit()

OUT = results_after_uncut, results_for_setting_covers

Thanks.

one workaround to try is to use the get_corrected_beam_length function with JoinGeometryUtils.SwitchJoinOrder in a transaction with rollback.

an example with a decorator

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

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

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

clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

import functools

cover_faces = {
    "Top": BuiltInParameter.CLEAR_COVER_TOP,
    "Bottom": BuiltInParameter.CLEAR_COVER_BOTTOM,
    "Other": BuiltInParameter.CLEAR_COVER_OTHER
}


def decoTransaction(commit):
    def subDecoTransaction(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            TransactionManager.Instance.ForceCloseTransaction()
            t = Transaction(doc, func.__name__)
            t.Start()
            ret = func(*args, **kwargs)
            if commit:
                t.Commit()
            else:
                t.RollBack()            
            t.Dispose()
            return ret      
        return wrapper  
    return subDecoTransaction   


def get_cover_type(distance):
    cover_types = FilteredElementCollector(doc).OfClass(RebarCoverType).ToElements()
    for ct in cover_types:
        if abs(ct.CoverDistance - distance) < 1e-6:
            return ct
    name = "Enrobage_{:.0f}cm".format(distance / 0.0328084)
    new_ct = RebarCoverType.Create(doc, name, distance)
    return new_ct

@decoTransaction(commit = True)
def set_beam_covers(beam, top, btm, side):
    cover_cm = {"Top": top, "Bottom": btm, "Other": side}
    cover_ft = {face: cover_cm[face] * 0.0328084 for face in cover_cm}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = get_cover_type(cover_ft[face])
            if ct:
                param.Set(ct.Id)
            else:
                print("test")
                
def get_beam_covers(beam):
    result = {}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = doc.GetElement(param.AsElementId())
            if isinstance(ct, RebarCoverType):
                result[face] = ct.CoverDistance * 30.48
    return result

def get_solid(elem):
    opt = Options()
    opt.ComputeReferences = True
    opt.IncludeNonVisibleObjects = True
    geoSet = elem.get_Geometry(opt)
    for geo in geoSet:
        if isinstance(geo, Solid) and geo.Volume > 0:
            return geo
    for geo in geoSet:
        if isinstance(geo, GeometryInstance):
            geoSetInst = geo.GetInstanceGeometry()
            for geoI in geoSetInst:
                if isinstance(geoI, Solid) and geoI.Volume > 0:
                    return geoI
    return None

@decoTransaction(commit = False)
def get_beam_covers_after_cutting(beam):

    cut_elems = [doc.GetElement(xId) for xId in SolidSolidCutUtils.GetCuttingSolids(beam)]
    for e in cut_elems:
        JoinGeometryUtils.SwitchJoinOrder(doc, beam, e)
    doc.Regenerate()

    solid = get_solid(beam)
    if not solid:
        return None
    return get_beam_covers(beam)    
        
beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .Where(System.Func[DB.Element, System.Boolean](lambda b: b.StructuralUsage == StructuralInstanceUsage.Girder))\
    .ToList()


results_after_uncut = []
results_for_setting_covers = []
results = []

for beam in beams:
    print(beam.Id)
    results_for_setting_covers.append(get_beam_covers_after_cutting(beam))
    set_beam_covers(beam, 10, 15, 8)
    results.append(get_beam_covers(beam))


OUT = results_after_uncut, results_for_setting_covers
2 Likes

Your workaround worked perfectly!.. by running the code below, I was able to update the cover parameters and compute both btm_curve and top_curve for each beam accordingly to these parameters as shown in the output below..this completely solved my issue.

beams network curves
import clr
import sys
import System
from System.Collections.Generic import IList, List
from System import Array
# ProtoGeometry
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

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

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

# Revit Services
clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument

# System.Core
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)
import functools

# Cover BuiltInParameter per beam faces
cover_faces = {
    "Top": BuiltInParameter.CLEAR_COVER_TOP,
    "Bottom": BuiltInParameter.CLEAR_COVER_BOTTOM,
    "Other": BuiltInParameter.CLEAR_COVER_OTHER
}

def decoTransaction(commit):
    def subDecoTransaction(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            TransactionManager.Instance.ForceCloseTransaction()
            t = Transaction(doc, func.__name__)
            t.Start()
            ret = func(*args, **kwargs)
            if commit:
                t.Commit()
            else:
                t.RollBack()            
            t.Dispose()
            return ret      
        return wrapper  
    return subDecoTransaction

cover_types = list(FilteredElementCollector(doc).OfClass(RebarCoverType).ToElements())    

# get or creat cover type 
def get_cover_type(distance):
    
    for ct in cover_types:
        if abs(ct.CoverDistance - distance) < 1e-6:
            return ct
    name = "Enrobage_{:.0f}cm".format(distance / 0.0328084)
    new_ct = RebarCoverType.Create(doc, name, distance)
    cover_types.append(new_ct)
    return new_ct

# subtransaction to set beam covers from user input
@decoTransaction(commit = True)
def set_beam_covers(beam, top, btm, side):
    cover_cm = {"Top": top, "Bottom": btm, "Other": side}
    cover_ft = {face: cover_cm[face] * 0.0328084 for face in cover_cm}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = get_cover_type(cover_ft[face])
            if ct:
                param.Set(ct.Id)
            else:
                print("test")
# get updated beam covers
def get_beam_covers(beam):
    result = {}
    for face, bip in cover_faces.items():
        param = beam.get_Parameter(bip)
        if param and param.StorageType == StorageType.ElementId:
            ct = doc.GetElement(param.AsElementId())
            if isinstance(ct, RebarCoverType):
                result[face] = ct.CoverDistance
    return result


# get solid geometry from beams
def get_solid(elem):
    opt = Options()
    opt.ComputeReferences = True
    opt.IncludeNonVisibleObjects = True
    geoSet = elem.get_Geometry(opt)
    for geo in geoSet:
        if isinstance(geo, Solid) and geo.Volume > 0:
            return geo
    for geo in geoSet:
        if isinstance(geo, GeometryInstance):
            geoSetInst = geo.GetInstanceGeometry()
            for geoI in geoSetInst:
                if isinstance(geoI, Solid) and geoI.Volume > 0:
                    return geoI
    return None

# Get the side face normal where the max edge belongs
def get_side_face_normal(solid, edge):
    for face in solid.Faces:
        if edge in [e for loop in face.EdgeLoops for e in loop]:
            normal = face.FaceNormal
            if not (normal.IsAlmostEqualTo(XYZ.BasisZ) or normal.IsAlmostEqualTo(-XYZ.BasisZ)):
                return normal
    return None

 
def is_curves_parallel(curve1, curve2, tolerance=1e-6):
    v1 = curve1.Direction.Normalize()
    v2 = curve2.Direction.Normalize()
    return v1.CrossProduct(v2).GetLength() <= tolerance


# Retrieve data from intersections between the current beam 
# and beams connected at its start and end points
def get_intersecting_end_beams_data(single_beam, all_beams, tolerance=1e-3):
    """Returns intersection and cover info using RevitAPI curve intersections only."""
    loc_curve = single_beam.Location.Curve
    w = single_beam.Symbol.LookupParameter("b").AsDouble()
    h = single_beam.Symbol.LookupParameter("h").AsDouble()

    covers = get_beam_covers(single_beam)
    top_cover = covers["Top"]
    btm_cover = covers["Bottom"]
    side_cover = covers["Other"]

    start = loc_curve.GetEndPoint(0)
    end = loc_curve.GetEndPoint(1)

    w_start = w_end = None
    start_side_cover = end_side_cover = None
    perpendicular_beams = []

    for beam in all_beams:
        if beam.Id == single_beam.Id:
            continue

        other_curve = beam.Location.Curve

        # Skip parallel
        if is_curves_parallel(loc_curve, other_curve):
            continue

        # RevitAPI intersection
        results = clr.Reference[IntersectionResultArray]()
        result = loc_curve.Intersect(other_curve, results)

        if result != SetComparisonResult.Overlap:
            continue

        intersection_points = []
        interResult = results.Value
        intersection_points.append(interResult[0].XYZPoint)

        if not intersection_points:
            continue

        # Handle each intersection point
        for pt in intersection_points:
            perpendicular_beams.append(Element.Name.GetValue(beam.Symbol))
            b = beam.Symbol.LookupParameter("b").AsDouble()

            other_covers = get_beam_covers(beam)
            other_side_cover = other_covers["Other"]

            # Check proximity to beam ends
            if start.DistanceTo(pt) < tolerance and w_start is None:
                w_start = b / 2
                start_side_cover = other_side_cover
            elif end.DistanceTo(pt) < tolerance and w_end is None:
                w_end = b / 2
                end_side_cover = other_side_cover

    beam_name = Element.Name.GetValue(single_beam.Symbol)
    beam_name = Element.Name.GetValue(single_beam.Symbol)
    print("Beam Type: {}".format(beam_name))
    print("  - Perpendicular beams:", perpendicular_beams)
    print("  - Start side cover in cm:", start_side_cover * 30.48)
    print("  - End side cover in cm:", end_side_cover * 30.48)
    print("-" * 50)

    return {
        "beam_width": w,
        "beam_height": h,
        "beam_top_cover": top_cover,
        "beam_btm_cover": btm_cover,
        "beam_side_cover": side_cover,
        "width_start": w_start,
        "width_end": w_end,
        "beam_start_side_cover": start_side_cover,
        "beam_end_side_cover": end_side_cover
    }

# Transform created "btm_curve" and "top_curve" to their location according to cover parameters
def curve_multiply_offset(curve, vector, intersection_data):
    h = intersection_data["beam_height"]
    top_cover = intersection_data["beam_top_cover"]
    btm_cover = intersection_data["beam_btm_cover"]
    side_cover = intersection_data["beam_side_cover"]

    curve = curve.CreateTransformed(Transform.CreateTranslation(XYZ(0, 0, -1).Multiply(top_cover)))
    vertical_offset = h - (top_cover + btm_cover)
    trans = Transform.CreateTranslation(vector.Negate().Multiply(side_cover))
    top_curve = curve.CreateTransformed(trans)
    bottom_trans = Transform.CreateTranslation(XYZ(0, 0, -1).Multiply(vertical_offset))
    btm_curve = top_curve.CreateTransformed(bottom_trans)
    btm_curve = Line.CreateBound(btm_curve.GetEndPoint(1), btm_curve.GetEndPoint(0))

    return top_curve, btm_curve

# main function to generate "btm_curve" and "top_curve" needed to create rebars.
@decoTransaction(commit = False)
def get_corrected_beam_length(beam, intersection_data, all_beams):
        
    beam_curve = beam.Location.Curve
    direction = beam_curve.Direction
    beam_length = beam_curve.Length
    start = beam_curve.GetEndPoint(0)

    cut_elems = [doc.GetElement(xId) for xId in SolidSolidCutUtils.GetCuttingSolids(beam)]
    for e in cut_elems:
        JoinGeometryUtils.SwitchJoinOrder(doc, beam, e)

    doc.Regenerate()

    solid = get_solid(beam)
    if not solid:
        return None

    # Pull intersection data
    beam_width = intersection_data["beam_width"]
    beam_height = intersection_data["beam_height"]
    top_cover = intersection_data["beam_top_cover"]
    btm_cover = intersection_data["beam_btm_cover"]
    side_cover = intersection_data["beam_side_cover"]
    w_start = intersection_data["width_start"] or 0
    w_end = intersection_data["width_end"] or 0
    start_side_cover = intersection_data["beam_start_side_cover"] or 0
    end_side_cover = intersection_data["beam_end_side_cover"] or 0

    # Identify main face and edge
    max_edge = max(solid.Edges, key=lambda x: x.ApproximateLength)
    max_edge_curve = max_edge.AsCurve()
    max_edge_direction = max_edge_curve.Direction
    max_edge_length = max_edge_curve.Length
    face_normal = get_side_face_normal(solid, max_edge)
    if face_normal is None:
        return None

    if max_edge_length > beam_length:
        if not max_edge_curve.GetEndPoint(0).Z == beam_curve.GetEndPoint(0).Z:
            max_edge_curve = Line.CreateBound(
                XYZ(max_edge_curve.GetEndPoint(0).X, max_edge_curve.GetEndPoint(0).Y, beam_curve.GetEndPoint(0).Z),
                XYZ(max_edge_curve.GetEndPoint(1).X, max_edge_curve.GetEndPoint(1).Y, beam_curve.GetEndPoint(1).Z)
            )
        if not max_edge_direction.IsAlmostEqualTo(direction):
            max_edge_curve = Line.CreateBound(max_edge_curve.GetEndPoint(1), max_edge_curve.GetEndPoint(0))

        if max_edge_length <= 1e-9:
            return None

        new_start = max_edge_curve.Evaluate(start_side_cover / max_edge_length, True)
        new_end = max_edge_curve.Evaluate(1 - (end_side_cover / max_edge_length), True)
        new_line = Line.CreateBound(new_start, new_end)

    else:
        total_length = beam_length + w_start + w_end - (start_side_cover + end_side_cover)
        project_point = start + face_normal.Multiply(beam_width / 2)
        new_start = project_point - direction.Multiply(w_start - start_side_cover)
        new_end = new_start + direction.Multiply(total_length)
        new_line = Line.CreateBound(new_start, new_end)

    # Generate top/bottom offset curves
    top_curve, btm_curve = curve_multiply_offset(new_line, face_normal, intersection_data)

    return btm_curve, top_curve

# Collect beams
beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .Where(System.Func[DB.Element, System.Boolean](lambda b: b.StructuralUsage == StructuralInstanceUsage.Girder))\
    .ToList()

btm_curves = []
top_curves = []

for beam in beams:
    set_beam_covers(beam, 10, 15, 8)

for beam in beams:
    
    intersection_data = get_intersecting_end_beams_data(beam, beams)
    result = get_corrected_beam_length(beam, intersection_data, beams)
    if not result:
        continue
    btm_curve, top_curve = result
    btm_curves.append(btm_curve)
    top_curves.append(top_curve)

OUT = [c.ToProtoType() for c in btm_curves], [c.ToProtoType() for c in top_curves]

Thanks a lot @c.poupin

Edited: can you suggest any improvements?

1 Like

No, not particularly.

Since you’re working on this code, you could try making a version with PythonNet3 (Revit 2025+). That would be one less thing to do in the future.

1 Like

I’m planning to install Revit 2025, and I’ll see how it goes with the use of PythonNet3.”

Thanks.